semlette / nfc_in_flutter

Cross-platform flutter plugin for reading and writing NFC tags. Not maintained anymore - not looking for new maintainer, fork instead.
MIT License
119 stars 119 forks source link

No tag ID in iOS #69

Closed JoeKaldas closed 3 years ago

JoeKaldas commented 3 years ago

Hello, I'm trying to retrieve the tag ID on iOS but always returns an empty string. It returns the correct ID on Android but nothing on iOS. I know this was mentioned before and there should be an update about it

mazensabouni commented 3 years ago

Hello, I’m also in need to this update.

JoeKaldas commented 3 years ago

Hello, I’m also in need to this update.

I managed to do it in the end for iOS using a native approach (swift) with flutter, using only this library for Android

mazensabouni commented 3 years ago

Did it worked?

JoeKaldas commented 3 years ago

Yes, it worked.

Flutter code

Future<void> getNFCTag() async {
    String message;
    try {
      var result = await platform.invokeMethod('getNFCTag');
      message = result;
    } on PlatformException catch (e) {
      message = "Error: ${e.message}'.";
    }
  }

Add this to appdelegate: NFCTagReaderSessionDelegate

Add this inside didFinishLaunchingWithOptions


let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    let nfcChannel = FlutterMethodChannel(name: "samples.flutter.dev/nfc", binaryMessenger: controller.binaryMessenger)
    nfcChannel.setMethodCallHandler({
        [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
      // Note: this method is invoked on the UI thread.
      guard call.method == "getNFCTag" else {
        result(FlutterMethodNotImplemented)
        return
      }
      self?.startReadingNFC(result: result)
    })

And those outside `didFinishLaunchingWithOptions`

func hexEncodedString(byteArray: [UInt8]) -> String {
        let hexDigits = Array("0123456789abcdef".utf16)
        var hexChars = [UTF16.CodeUnit]()
        hexChars.reserveCapacity(byteArray.count * 2)
        for byte in byteArray {
          let (index1, index2) = Int(byte).quotientAndRemainder(dividingBy: 16)
          hexChars.append(hexDigits[index1])
          hexChars.append(hexDigits[index2])
        }
        return String(utf16CodeUnits: hexChars, count: hexChars.count)
    }

    private func startReadingNFC(result: @escaping FlutterResult) {
        flutterResult = result
        session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self, queue: DispatchQueue.main)
        session?.begin()
    }

    func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {

    }

    func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
        if case let NFCTag.miFare(tag) = tags.first! {
            session.connect(to: tags.first!) { (error: Error?) in
                let tagUIDData = tag.identifier
                var byteData: [UInt8] = []
                tagUIDData.withUnsafeBytes { byteData.append(contentsOf: $0) }
                session.invalidate()
                DispatchQueue.main.async {
                    flutterResult(self.hexEncodedString(byteArray: byteData))
                }
            }
        }
    }

    func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
        DispatchQueue.main.async {
            session.invalidate()
            print(error.localizedDescription)
            flutterResult(error.localizedDescription)
        }
    }
mazensabouni commented 3 years ago

THANK YOU SO MUCH!

JoeKaldas commented 3 years ago

THANK YOU SO MUCH!

Did it work for you?

diarmuidmcg commented 3 years ago

Hey Joe,

Trying to implement this now, thank you so much for sharing this! However, I'm running into a syntax error in xcode surrounding flutterResult. Do i need to define that somewhere in the AppDelegate.swift file or even import a library?

If you want to share screenshots of your code so I can follow along, that would be amazing!

thank you!

diarmuidmcg commented 3 years ago

i'm getting a "cannot find 'flutterResult' in scope error"

diarmuidmcg commented 3 years ago

Got it working! Added "var flutterResult: FlutterResult! var session: NFCTagReaderSession!" to the class & that fixed the syntax errors. Also shared a screenshot from my AppDelegate.swift in case anyone else has any issues.

Screen Shot 2020-12-28 at 4 53 24 PM

hanabon commented 3 years ago

@diarmuidmcg Thanks for the screenshot, I'm still somehow getting errors:

error: reference to property 'flutterResult' in closure requires explicit use of 'self' to make capture semantics explicit error: cannot find 'controller' in scope

any ideas on how to fix them?

diarmuidmcg commented 3 years ago

yes! include this line inside didFinishLaunchingWithOptions:

let controller = window?.rootViewController as! FlutterViewController

Screen Shot 2020-12-28 at 6 19 56 PM

JoeKaldas commented 3 years ago

Hey @diarmuidmcg I'll send you a screenshot tonight.

I did both approaches for Swift.

  1. Getting the tag ID
  2. Getting the payload/messages on the tag. I used this approach as I was scanning MiFare Classic cards which aren't detected by iOS unless they have a payload. (And even if they have message, they're never detected when trying to fetch the ID approach)
hanabon commented 3 years ago

@diarmuidmcg @JoeKaldas Thank you so much, everything works now. Could you share how to get both the tag ID and the payload?

JoeKaldas commented 3 years ago

@hanabon Yes, I'll share it as soon as I'm home tonight.

JoeKaldas commented 3 years ago

Here is the code for detecting payload/message (I'm only detecting first message only) Code for detecting tag ID is after


import UIKit
import Flutter
import CoreNFC

var session: NFCNDEFReaderSession?
var flutterResult: FlutterResult!

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, NFCNDEFReaderSessionDelegate {

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {

    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    let nfcChannel = FlutterMethodChannel(name: "samples.flutter.dev/nfc", binaryMessenger: controller.binaryMessenger)
    nfcChannel.setMethodCallHandler({
        [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
      // Note: this method is invoked on the UI thread.
      guard call.method == "getNFCTag" else {
        result(FlutterMethodNotImplemented)
        return
      }
      self?.startReadingNFC(result: result)
    })

    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

    func hexEncodedString(byteArray: [UInt8]) -> String {
        let hexDigits = Array("0123456789abcdef".utf16)
        var hexChars = [UTF16.CodeUnit]()
        hexChars.reserveCapacity(byteArray.count * 2)

        for byte in byteArray {
          let (index1, index2) = Int(byte).quotientAndRemainder(dividingBy: 16)
          hexChars.append(hexDigits[index1])
          hexChars.append(hexDigits[index2])
        }

        return String(utf16CodeUnits: hexChars, count: hexChars.count)
    }

    private func startReadingNFC(result: @escaping FlutterResult) {
        flutterResult = result
        session = NFCNDEFReaderSession(delegate: self, queue: DispatchQueue.main, invalidateAfterFirstRead: true)
        session?.begin()
    }

    func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
        if (tags.count > 0) {
            session.connect(to: tags.first!, completionHandler: { (connection_error) in
                tags.first?.readNDEF(completionHandler: { (message, error) in
                    if ((message?.records.count)! > 0) {
                        if let dataMessage = String(data: (message?.records.first!.payload)!, encoding:.utf8) {
                            session.invalidate()
                            DispatchQueue.main.async {
                                flutterResult(dataMessage)
                            }
                        }
                    }
                })
            })
        }
    }

    func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
    }

    func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
        session.invalidate()
        DispatchQueue.main.async {
            flutterResult(error.localizedDescription)
        }
    }
}

Here is the code for detecting the tag ID:


import UIKit
import Flutter
import CoreNFC

var session_tag: NFCTagReaderSession?
var flutterResult: FlutterResult!

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate, NFCTagReaderSessionDelegate {

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {

    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    let nfcChannel = FlutterMethodChannel(name: "samples.flutter.dev/nfc", binaryMessenger: controller.binaryMessenger)
    nfcChannel.setMethodCallHandler({
        [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
      // Note: this method is invoked on the UI thread.
      guard call.method == "getNFCTag" else {
        result(FlutterMethodNotImplemented)
        return
      }
      self?.startReadingNFC(result: result)
    })

    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

    func hexEncodedString(byteArray: [UInt8]) -> String {
        let hexDigits = Array("0123456789abcdef".utf16)
        var hexChars = [UTF16.CodeUnit]()
        hexChars.reserveCapacity(byteArray.count * 2)

        for byte in byteArray {
          let (index1, index2) = Int(byte).quotientAndRemainder(dividingBy: 16)
          hexChars.append(hexDigits[index1])
          hexChars.append(hexDigits[index2])
        }

        return String(utf16CodeUnits: hexChars, count: hexChars.count)
    }

    private func startReadingNFC(result: @escaping FlutterResult) {
        flutterResult = result
        session_tag = NFCTagReaderSession(pollingOption: .iso14443, delegate: self, queue: DispatchQueue.main)
        session_tag?.begin()
    }

    func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
        print("Tag session active")
    }

    func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
        print("Did detect tag reader session")
        print(tags.count)
        if case let NFCTag.miFare(tag) = tags.first! {
            session.connect(to: tags.first!) { (error: Error?) in
                let tagUIDData = tag.identifier
                var byteData: [UInt8] = []
                tagUIDData.withUnsafeBytes { byteData.append(contentsOf: $0) }
                session.invalidate()
                DispatchQueue.main.async {
                    flutterResult(self.hexEncodedString(byteArray: byteData))
                }
            }
        }
    }

    func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
        print("Tag Error: \(error.localizedDescription)")
        DispatchQueue.main.async {
            session.invalidate()
            print(error.localizedDescription)
            flutterResult(error.localizedDescription)
        }
    }
}
mazensabouni commented 3 years ago

Hello All, Thank you so much for your effort I really appreciate. I'm new to this, can anybody tell me how to combine both codes to get the id and payload at the same time? As I can't combine the getter for id with the normal nfc_flutter package. Any help?

mazensabouni commented 3 years ago

Or how to use it with the package? as when i combine both your code and the package only one session at a time works and when I try to access the package code the invokedMethod is hidden. Also being able to show a text on the reading pop up through your method will be appreciated. Thank you

mazensabouni commented 3 years ago

Guys please

JoeKaldas commented 3 years ago

Hello @mazensabouni ,

For Android using this library, you have

For iOS, you should be able to use same thing.

I ran into the issue when trying to detect tag ID on iOS because I was using certain Mifare version that wasn't supported so had to do this workaround.

PiroX4256 commented 3 years ago

Hi there, thanks for the solution. I did not understand a thing.

I put the code you provided for reading the tag id in AppDelegate.swift (ios/runner folder), but I did not understand how to call it from my flutter function inside a .dart file.

Can you help me? Thanks!

PiroX4256 commented 3 years ago

@JoeKaldas I think I have to ask this to you

JoeKaldas commented 3 years ago

@PiroX4256

So in AppDelegate. you added the following

let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    let nfcChannel = FlutterMethodChannel(name: "samples.flutter.dev/nfc", binaryMessenger: controller.binaryMessenger)
    nfcChannel.setMethodCallHandler({
        [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
      // Note: this method is invoked on the UI thread.
      guard call.method == "getNFCTag" else {
        result(FlutterMethodNotImplemented)
        return
      }
      self?.startReadingNFC(result: result)
    })

Notice the "getNFCTag" part, this is the name of your function that you will define in Flutter.

So in your flutter app, you can have the following, that you call upon clicking "scan" for example

Future<void> getNFCTag() async {
    String message;
    try {
      var result = await platform.invokeMethod('getNFCTag');
      message = result;
    } on PlatformException catch (e) {
      message = "Error: ${e.message}'.";
    }
  }
PiroX4256 commented 3 years ago

Ok, now it opens the iOS tag scanning windows. The problem is that it does not detect my tag, which is a Mifare Classic 1k. Do you have an idea on how make it detecting my card?

JoeKaldas commented 3 years ago

@PiroX4256 Yes, I think you need to write data on the card first. So in my case, I scanned the card using any NFC app "NFC Tools" for example, then write the detected tag on the card.

PiroX4256 commented 3 years ago

@JoeKaldas I have written a plain text field on my card, but my phone still does not recognize it. It works with nfc tool but only in compatibility mode. For my purposes I only need to extract tag id from my nfc tag. Is it possible? Thanks again

JoeKaldas commented 3 years ago

@PiroX4256 I think this was my issue and I did a workaround by writing the card's tag id on the card itself.

PiroX4256 commented 3 years ago

Oh well, okay. Thank you.

SerjKli commented 1 year ago

Hello there! Guys, I need your help, please.

What do I need: to get the tag's UID using iOS app What do I have: I have an app Flutter, which works fine on Android (I can read the tag's UID) and do nothing on iOS

Details: I have this type of cards (plastic card) image

As you can see, this is Mifare Classic 1k card with payload "My text with spaces"

I used flutter channels to communicate between flutter and iOS native (swift)

On iOS side I created two implementations: one for NFCTagReaderSession, another one for NFCNDEFReaderSession (I do not use it at the same time, only separated) Both approaches start fine and open the iOS's window with "read a tag", but with NFCTagReaderSession nothing is happening. It seems like there is no card. With NFCNDEFReaderSession implementation, I can read the message "My text with spaces" but, of course, I can't read the tag's UID

What i'm doing wrong?