반응형

NFC NDEF tag 및 feliCa, iso7816, iso15693, miFare tag  읽기 / 쓰기 관련 클래스를 만들어 보았다.

//
//  HiNfcManager.swift
//  NFC
//
//  Created by netcanis on 2023/04/18.
//

import UIKit
import CoreNFC

public enum HiNFCError: Error {
    case unavailable
    case notSupported
    case readOnly
    case invalidPayloadSize
    case invalidated(errorDescription: String)
}

open class HiNFCManager: NSObject {
    static let shared = HiNFCManager()
    
    public typealias DidBecomeActive = (HiNFCManager) -> Void
    public typealias DidDetect = (HiNFCManager, Result<[String: Any]?, HiNFCError>) -> Void
    
    private enum HiNFCAction {
        case read
        case write(message: NFCNDEFMessage)
    }
    
    // MARK: - Properties
    private var didBecomeActive: DidBecomeActive?
    private var didDetect: DidDetect?
    private var action: HiNFCAction?
    
    // MARK: - Properties (NDEF, Tag)
    open private(set) var ndefSession: NFCNDEFReaderSession?
    open private(set) var tagSession: Any?
    
    
    
    // MARK: - NFCNDEFTag
    open func read(didBecomeActive: DidBecomeActive? = nil, didDetect: @escaping DidDetect) {
        guard NFCNDEFReaderSession.readingAvailable else {
            self.didDetect?(self, .failure(.unavailable))
            return
        }
        let session = NFCNDEFReaderSession(delegate: self,
                                           queue: nil,
                                           invalidateAfterFirstRead: true)
        action = .read
        startSession(session: session, didBecomeActive: didBecomeActive, didDetect: didDetect)
    }
    
    open func write(message: [String: Any], didBecomeActive: DidBecomeActive? = nil, didDetect: @escaping DidDetect) {
        guard NFCNDEFReaderSession.readingAvailable else {
            self.didDetect?(self, .failure(.unavailable))
            return
        }
        if #available(iOS 13.0, *) {
            let session = NFCNDEFReaderSession(delegate: self,
                                               queue: nil,
                                               invalidateAfterFirstRead: false)
            
            let payload = message["payload"] as? String ?? ""
            let type = message["type"] as? String ?? "T"
            let format: NFCTypeNameFormat = (type == "T") ? (.media) : (.absoluteURI)
            
            let payloadData = payload.data(using: .utf8)!
            let typeData = type.data(using: .utf8)!
            let ndefPayload = NFCNDEFPayload(format: format, type: typeData, identifier: Data(), payload: payloadData)
            let ndefMessage = NFCNDEFMessage(records: [ndefPayload])
            
            action = .write(message: ndefMessage)
            startSession(session: session, didBecomeActive: didBecomeActive, didDetect: didDetect)
        }
    }
    
    open func setMessage(_ alertMessage: String) {
        ndefSession?.alertMessage = alertMessage
    }
    
    
    
    // MARK: - NFCTag
    open func readTag(didBecomeActive: DidBecomeActive? = nil, didDetect: @escaping DidDetect) {
        guard NFCReaderSession.readingAvailable else {
            self.didDetect?(self, .failure(.unavailable))
            return
        }
        if #available(iOS 13.0, *) {
            let session = NFCTagReaderSession(pollingOption: [.iso14443, .iso15693, .iso18092],
                                              delegate: self,
                                              queue: nil)!
            action = .read
            startSession(session: session, didBecomeActive: didBecomeActive, didDetect: didDetect)
        }
    }
    
    open func writeTag(message: [String: Any], didBecomeActive: DidBecomeActive? = nil, didDetect: @escaping DidDetect) {
        guard NFCReaderSession.readingAvailable else {
            self.didDetect?(self, .failure(.unavailable))
            return
        }
        if #available(iOS 13.0, *) {
            let session = NFCTagReaderSession(pollingOption: [.iso14443, .iso15693, .iso18092],
                                              delegate: self,
                                              queue: nil)!
            
            let payload = message["payload"] as? String ?? ""
            let type = message["type"] as? String ?? "T"
            let format: NFCTypeNameFormat = (type == "T") ? (.media) : (.absoluteURI)
            
            let payloadData = payload.data(using: .utf8)!
            let typeData = type.data(using: .utf8)!
            let ndefPayload = NFCNDEFPayload(format: format, type: typeData, identifier: Data(), payload: payloadData)
            let ndefMessage = NFCNDEFMessage(records: [ndefPayload])

            action = .write(message: ndefMessage)
            startSession(session: session, didBecomeActive: didBecomeActive, didDetect: didDetect)
        }
    }
    
    open func setTagMessage(_ alertMessage: String) {
        if #available(iOS 13.0, *) {
            if let session = self.tagSession as? NFCTagReaderSession {
                session.alertMessage = alertMessage
            }
        }
    }
}



// MARK: - NFCNDEFReaderSessionDelegate
extension HiNFCManager : NFCNDEFReaderSessionDelegate {
    
    open func readerSessionDidBecomeActive(_ session: NFCNDEFReaderSession) {
        self.didBecomeActive?(self)
    }
    
    open func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
        // iOS 13미만
        guard let message = messages.first, let record = message.records.first else {
            self.didDetect?(self, .failure(.invalidated(errorDescription: "NDEF message나 message record가 없습니다.")))
            self.invalidate(errorMessage: "NDEF message나 message record가 없습니다.")
            return
        }
        
        
        let language = String(data: record.payload.advanced(by: 1), encoding: .utf8)
        let encoding = record.payload[0] & NFCTypeNameFormat.nfcWellKnown.rawValue
        let textData = record.payload.advanced(by: 3)
        let text = String(data: textData, encoding: .utf8)
        
        let result: [String: Any] = [
            "Type":record.type.string,
            "Format": self.formattedTNF(from: record.typeNameFormat),
            "Value": [
                "Encoding": "\(encoding)",
                "Language": "\(language ?? "")",
                "Text": "\(text ?? "")"
            ],
            "Raw value": record.payload.string,
            "Payload": "\(record.payload.hexStringFormatted)",
            "Size": "\(record.payload.count)",
        ]
        print("\(String(describing: result.toJsonString()))")
        
        self.didDetect?(self, .success(result))
        self.invalidate(errorMessage: "NDEF message read successfully.")
    }
    
    @available(iOS 13.0, *)
    open func readerSession(_ session: NFCNDEFReaderSession, didDetect tags: [NFCNDEFTag]) {
        guard tags.count == 1, let tag = tags.first else {
            DispatchQueue.global().asyncAfter(deadline: .now() + .microseconds(500)) {
                session.restartPolling()
            }
            return
        }
        
        session.connect(to: tag) { [weak self] error in
            guard let self = self else { return }
            if error != nil {
                self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                self.invalidate(errorMessage: error?.localizedDescription)
                return
            }
            
            tag.queryNDEFStatus { status, capacity, error in
                switch (status, self.action) {
                case (.notSupported, _):
                    self.didDetect?(self, .failure(.notSupported))
                    self.invalidate(errorMessage: error?.localizedDescription)

                case (.readOnly, _):
                    self.didDetect?(self, .failure(.readOnly))

                case (.readWrite, .read):
                    tag.readNDEF { message, error in
                        if error != nil {
                            self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                            self.invalidate(errorMessage: error?.localizedDescription)
                            return
                        }
                        
                        let record = message?.records.first
                        let result: [String: Any] = [
                            "Type":record?.type.string ?? "",
                            "Format": self.formattedTNF(from: record!.typeNameFormat),
                            "Raw value": record?.payload.string ?? "",
                            "Payload": "\(record?.payload.hexStringFormatted ?? "")",
                            "Size": "\(record?.payload.count ?? 0)",
                        ]
                        self.didDetect?(self, .success(result))
                        self.invalidate(errorMessage: error?.localizedDescription)
                    }

                case (.readWrite, .write(let message)):
                    guard message.length <= capacity else {
                        self.didDetect?(self, .failure(.invalidPayloadSize))
                        self.invalidate(errorMessage: "Invalid payload size")
                        return
                    }

                    tag.writeNDEF(message) { error in
                        if error != nil {
                            self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                            self.invalidate(errorMessage: error!.localizedDescription)
                            return
                        }
                        let result: [String: Any] = [
                            "result":message.records.first?.payload.string ?? ""
                        ]
                        self.didDetect?(self, .success(result))
                        self.invalidate(errorMessage: error?.localizedDescription)
                    }
                default:
                    return
                }
            }
        }
    }
    
    open func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
        if let error = error as? NFCReaderError,
           error.code != .readerSessionInvalidationErrorFirstNDEFTagRead &&
            error.code != .readerSessionInvalidationErrorUserCanceled {
            self.didDetect?(self, .failure(.invalidated(errorDescription: error.localizedDescription)))
        }
        self.ndefSession = nil
        self.tagSession = nil
        self.didBecomeActive = nil
        self.didDetect = nil
    }
}


// MARK: - Private helper functions
extension HiNFCManager {
    private func invalidate(errorMessage: String?) {
        if errorMessage != nil {
            if #available(iOS 13.0, *) {
                if let ns = ndefSession {
                    ns.invalidate(errorMessage: errorMessage!)
                }
                if let ts = tagSession as? NFCTagReaderSession {
                    ts.invalidate(errorMessage: errorMessage!)
                }
            } else {
                if let ns = ndefSession {
                    ns.invalidate()
                }
                if #available(iOS 13.0, *) {
                    if let ts = tagSession as? NFCTagReaderSession {
                        ts.invalidate()
                    }
                }
            }
        } else {
            if let ns = ndefSession {
                ns.invalidate()
            }
            if #available(iOS 13.0, *) {
                if let ts = tagSession as? NFCTagReaderSession {
                    ts.invalidate()
                }
            }
        }
        ndefSession = nil
        tagSession = nil
        didBecomeActive = nil
        didDetect = nil
    }
    
    private func startSession(session: NFCNDEFReaderSession,
                              didBecomeActive: DidBecomeActive?,
                              didDetect: @escaping DidDetect) {
        self.ndefSession = session
        self.didBecomeActive = didBecomeActive
        self.didDetect = didDetect
        session.begin()
    }
    
    @available(iOS 13.0, *)
    private func startSession(session: NFCTagReaderSession,
                              didBecomeActive: DidBecomeActive?,
                              didDetect: @escaping DidDetect) {
        self.tagSession = session
        self.didBecomeActive = didBecomeActive
        self.didDetect = didDetect
        session.begin()
    }
    
    private func formattedTNF(from tnf: NFCTypeNameFormat) -> String {
        switch tnf {
        case .empty:        // 0: 이름 없는 레코드 (빈 문자열로 표시됨)
            return "Empty (0x00)"
        case .nfcWellKnown: // 1: NFC Forum에서 정의한 레코드 형식
            return "NFC Well Known (0x01)"
        case .media:        // 2: 미디어 타입의 레코드 (ex. 'audio/mp3')
            return "Media (0x02)"
        case .absoluteURI:  // 3: URI 형식의 레코드
            return "Absolute URI (0x03)"
        case .nfcExternal:  // 4: NFC 포럼에서 정의한 외부 레코드
            return "NFC External (0x04)"
        case .unchanged:    // 6: 이름 형식 변경 없음. (현재의 이름 형식을 유지)
            return "Unchanged (0x06)"
        default:            // 5: 알 수 없는 레코드
            return "Unknown (0x05)"
        }
    }
}





// MARK: - NFCTagReaderSessionDelegate
@available(iOS 13.0, *)
extension HiNFCManager : NFCTagReaderSessionDelegate {
    
    public func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
        self.didBecomeActive?(self)
    }
    
    public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
        guard tags.count == 1, let tag = tags.first else {
            DispatchQueue.global().asyncAfter(deadline: .now() + .microseconds(500)) {
                session.restartPolling()
            }
            return
        }
        
        var ndefTag: NFCNDEFTag
        switch tag {
        case let .feliCa(tag):  /// FeliCa tag. (NFCFeliCaTag)
            ndefTag = tag
            self.parseFeliCaTag(tag)
        case let .iso7816(tag): /// ISO14443-4 type A / B tag with ISO7816 communication. (NFCISO7816Tag)
            ndefTag = tag
            self.parseISO7816Tag(tag)
        case let .iso15693(tag):/// ISO15693 tag.
            ndefTag = tag
            self.parseISO15693Tag(tag)
        case let .miFare(tag):  /// MiFare technology tag (MIFARE Plus, UltraLight, DESFire) base on ISO14443. (NFCMiFareTag)
            ndefTag = tag
            self.parseMIFARETag(tag)
        @unknown default:
            self.didDetect?(self, .failure(.invalidated(errorDescription: "Tag not valid.")))
            self.invalidate(errorMessage: "Tag not valid.")
            return
        }
        
        
        session.connect(to: tag) { [weak self] error in
            guard let self = self else { return }
            if error != nil {
                self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                self.invalidate(errorMessage: error?.localizedDescription)
                return
            }
            
            ndefTag.queryNDEFStatus { status, capacity, error in
                switch (status, self.action) {
                case (.notSupported, _):
                    self.didDetect?(self, .failure(.notSupported))
                    self.invalidate(errorMessage: error?.localizedDescription)

                case (.readOnly, _):
                    self.didDetect?(self, .failure(.readOnly))

                case (.readWrite, .read):
                    ndefTag.readNDEF { (message, error) in
                        if error != nil || message == nil {
                            self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                            self.invalidate(errorMessage: error?.localizedDescription)
                            return
                        }

                        let record = message?.records.first
                        let result: [String: Any] = [
                            "Type":record?.type.string ?? "",
                            "Format": self.formattedTNF(from: record!.typeNameFormat),
                            "Raw value": record?.payload.string ?? "",
                            "Payload": "\(record?.payload.hexStringFormatted ?? "")",
                            "Size": "\(record?.payload.count ?? 0)",
                        ]
                        self.didDetect?(self, .success(result))
                        self.invalidate(errorMessage: error?.localizedDescription)
                    }
                case (.readWrite, .write(let message)):
                    guard message.length <= capacity else {
                        self.didDetect?(self, .failure(.invalidPayloadSize))
                        self.invalidate(errorMessage: "Invalid payload size")
                        return
                    }

                    ndefTag.writeNDEF(message) { error in
                        if error != nil {
                            self.didDetect?(self, .failure(.invalidated(errorDescription: error!.localizedDescription)))
                            self.invalidate(errorMessage: error!.localizedDescription)
                            return
                        }
                        let result: [String: Any] = [
                            "result":message.records.first?.payload.string ?? ""
                        ]
                        self.didDetect?(self, .success(result))
                        self.invalidate(errorMessage: error?.localizedDescription)
                    }
                default:
                    return
                }
            }
        }
    }
    
    public func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
        // NFC 태그 읽기가 중단되었을 때, 호출된다.
        print("NFCTag 읽기 중단: \(error.localizedDescription)")
    }
    
}


// MARK: - Private helper functions (for NFCTag)
@available(iOS 13.0, *)
extension HiNFCManager {
    /// FeliCa tag. (NFCFeliCaTag)
    private func parseFeliCaTag(_ tag: NFCFeliCaTag) {
        let log = """
                    ------------------------------------
                    :::::::::: [ FeliCa tag ] ::::::::::
                    "- Type : FeliCa tag."
                    "- currentIDm : \(String(describing: tag.currentIDm))"
                    "- currentSystemCode : \(String(describing: tag.currentSystemCode))"
                    "- description : \(String(describing: tag.description))"
                    ------------------------------------
                    """
        print(log)
    }
    
    /// ISO14443-4 type A / B tag with ISO7816 communication. (NFCISO7816Tag)
    private func parseISO7816Tag(_ tag: NFCISO7816Tag) {
        let log = """
                    ------------------------------------
                    :::::::::: [ ISO7816 tag ] ::::::::::
                    "- Type : ISO14443-4 type A / B tag with ISO7816 communication."
                    "- Identifier : \(String(describing: tag.identifier))"
                    "- historicalBytes : \(String(describing: tag.historicalBytes?.hexStringFormatted))"
                    "- applicationData : \(String(describing: tag.applicationData?.hexStringFormatted))"
                    "- initialSelectedAID : \(tag.initialSelectedAID)"
                    "- proprietaryApplicationDataCoding : \(tag.proprietaryApplicationDataCoding)"
                    "- description : \(String(describing: tag.description))"
                    ------------------------------------
                    """
        print(log)
    }

    /// ISO15693 tag.
    private func parseISO15693Tag(_ tag: NFCISO15693Tag) {
        let log = """
                    ------------------------------------
                    :::::::::: [ ISO15693 tag ] ::::::::::
                    "- Type : ISO15693 tag."
                    "- Identifier : \(tag.identifier.hexString)"
                    "- icSerialNumber : \(String(describing: tag.icSerialNumber.hexStringFormatted))" // IC 시리얼 번호(IC serial number)
                    "- icManufacturerCode : \(String(describing: tag.icManufacturerCode))" // C 제조사 코드(IC manufacturer code)
                    "- description : \(String(describing: tag.description))"
                    ------------------------------------
                    """
        print(log)
    }
    
    /// MiFare technology tag (MIFARE Plus, UltraLight, DESFire) base on ISO14443. (NFCMiFareTag)
    private func parseMIFARETag(_ tag: NFCMiFareTag) {
        let log = """
                    ------------------------------------
                    :::::::::: [ MiFare tag ] ::::::::::
                    "- Type : MiFare technology tag (MIFARE Plus, UltraLight, DESFire) base on ISO14443."
                    "- Identifier : \(tag.identifier.hexString)"
                    "- Historical bytes : \(tag.historicalBytes?.hexString ?? "None")"
                    "- mifareFamily : \(self.formattedMiFareFamily(tag))"
                    "- description : \(String(describing: tag.description))"
                    "- Block count : \(tag.mifareFamily == .plus ? 256 : 16)"
                    ------------------------------------
                    """
        print(log)
    }
    
    private func formattedMiFareFamily (_ tag: NFCMiFareTag) -> String {
        switch (tag.mifareFamily) {
        case .unknown:
            return "MiFare compatible ISO14443 Type A tag." // ISO14443 Type A 호환제품
        case .ultralight:
            return "MiFare Ultralight series."
        case .plus:
            return "MiFare Plus series."
        case .desfire:
            return "MiFare DESFire series."
        default:
            return "Unknown"
        }
    }
}



extension Data {
    // Data([0x12, 0x34, 0x56]) -> 123456
    var hexString: String {
        return map { String(format: "%02hhx", $0) }.joined() // big-endian byte order : reversed().map
    }
    
    // Data([0x12, 0x34, 0x56]) -> 0x12 0x34 0x56
    var hexStringFormatted: String {
        let hexArray = map { String(format: "0x%02hhx", $0) }
        return hexArray.joined(separator: " ")
    }
    
    // Data([0x12, 0x34, 0x56]) -> 313233343536
    public func hexEncodedString() -> String {
        let hexDigits = Array("0123456789abcdef".utf16)
        var hexChars = [UTF16.CodeUnit]()
        hexChars.reserveCapacity(count * 2)
        for byte in self {
            let (index1, index2) = Int(byte).quotientAndRemainder(dividingBy: 16)
            hexChars.append(hexDigits[index1])
            hexChars.append(hexDigits[index2])
        }
        return String(utf16CodeUnits: hexChars, count: hexChars.count)
    }
    
    var string: String {
        return String(data: self, encoding: .utf8)!
    }
    
    var jsonString: String {
        do {
            let json = try JSONSerialization.jsonObject(with: self, options: [])
            let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
            return String(data: data, encoding: .utf8) ?? ""
        } catch let error {
            print("JSON serialization error: \(error.localizedDescription)")
            return ""
        }
    }
    
    func isJsonString() -> Bool {
        do {
            let _ = try JSONSerialization.jsonObject(with: self, options: [])
            return true
        } catch {
            return false
        }
    }
}

extension Dictionary {
    func toJsonString() -> String? {
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: self, options: [.prettyPrinted, .sortedKeys])
            return String(data: jsonData, encoding: .utf8)
        } catch {
            print(error.localizedDescription)
            return nil
        }
    }
}

extension Array where Element == Data {
    func toStrings() -> [String] {
        return self.map { String(data: $0, encoding: .utf8) ?? "" }
    }
    
    func toHexStrings() -> [String] {
        return self.map { $0.reduce("") { $0 + String(format: "%02x", $1) } }
    }
}

extension NSObject {
    func rootWindow() -> UIWindow? {
        var window: UIWindow?
        if #available(iOS 15.0, *) {
            window = UIApplication.shared.connectedScenes
                .compactMap { $0 as? UIWindowScene }
                .flatMap { $0.windows }
                .first(where: { $0.isKeyWindow })
        } else {
            window = UIApplication.shared.windows.first(where: { $0.isKeyWindow })
        }
        return window
    }
    
    func showAlert(title: String?, message: String?, actions: [UIAlertAction] = [UIAlertAction(title: "OK", style: .default, handler: nil)], preferredStyle: UIAlertController.Style = .alert) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: preferredStyle)
        for action in actions {
            alertController.addAction(action)
        }
        guard let rootViewController = self.rootWindow()?.rootViewController else { return }
        rootViewController.present(alertController, animated: true)
    }
}

 

사용법 :

// NFC NDEF 테그 쓰기
let str = "테스트 메시지 입니다."
let type = str.hasPrefix("http") == true ? "U" : "T"

let message: [String: Any] = [
    "payload": str,
    "type": type
]
HiNFCManager.shared.write(message: message) { manager in
    manager.setMessage("Place iPhone near the tag to be written on")
} didDetect: { manager, result in
    switch result {
    case .failure(let error):
        manager.setMessage("Failed to write tag")
        print("\(error.localizedDescription)")
    case .success(let payload):
        manager.setMessage("Tag successfully written")
        print("\(payload?.toJsonString() ?? "")")
        DispatchQueue.main.async {
            self.infoText.text = "\(payload?.toJsonString() ?? "")" + "\n" + self.infoText.text
        }
   }
}


// NFC NDEF 테그 읽기
HiNFCManager.shared.read { manager, result in
    switch result {
    case .failure(let error):
        manager.setMessage("Failed to read tag")
        print("\(error.localizedDescription)")
    case .success(let payload):
        manager.setMessage("Tag read successfully")
        print("\(payload?.toJsonString() ?? "")")
        DispatchQueue.main.async {
            self.infoText.text = "\(payload?.toJsonString() ?? "")" + "\n" + self.infoText.text
        }
   }
}




// NFC Tag 쓰기
let str = "https://www.apple.com"
let type = str.hasPrefix("http") == true ? "U" : "T"

let message: [String: Any] = [
    "payload": str,
    "type": type
]
HiNFCManager.shared.writeTag(message: message) { manager in
    manager.setMessage("Place iPhone near the tag to be written on")
} didDetect: { manager, result in
    switch result {
    case .failure(let error):
        manager.setMessage("Failed to write tag")
        print("\(error.localizedDescription)")
    case .success(let payload):
        manager.setMessage("Tag successfully written")
        print("\(payload?.toJsonString() ?? "")")
        DispatchQueue.main.async {
            self.infoText.text = "\(payload?.toJsonString() ?? "")" + "\n" + self.infoText.text
        }
   }
}


// NFC Tag 읽기
HiNFCManager.shared.readTag { manager, result in
    switch result {
    case .failure(let error):
        manager.setMessage("Failed to read tag")
        print("\(error.localizedDescription)")
    case .success(let payload):
        manager.setMessage("Tag read successfully")
        print("\(payload?.toJsonString() ?? "")")
        DispatchQueue.main.async {
            self.infoText.text = "\(payload?.toJsonString() ?? "")" + "\n" + self.infoText.text
        }
   }
}

 

결과값 로그 :

// NFC NDEF tag 읽기결과 로그
{
  "Format" : "Media (0x02)",
  "Payload" : "0x74 0x65 0x73 0x74 0x20 0x6d 0x65 0x73 0x73 0x61 0x67 0x65",
  "Raw value" : "test message",
  "Size" : "12",
  "Type" : "T"
}

// NFC NDEF tag 쓰기결과 로그
{
  "result" : "test message"
}


// NFC Tag 읽기결과 로그
{
  "Format" : "Absolute URI (0x03)",
  "Payload" : "0x68 0x74 0x74 0x70 0x73 ... 0x65 0x2e 0x63 0x6f 0x6d",
  "Raw value" : "https://www.apple.com",
  "Size" : "21",
  "Type" : "U"
}

// NFC Tag 쓰기결과 로그
{
  "result" : "https:\/\/www.apple.com"
}

 

 

2023.04.26 - [iOS] - NFC tag read/write Manager Class (1/2)

2023.04.26 - [iOS] - NFC tag read/write Manager Class (2/2)

 

반응형

'개발 > iOS' 카테고리의 다른 글

XOR 연산을 이용한 문자열 비교  (0) 2023.12.07
Rosetta2  (0) 2023.04.26
NFC tag read/write Manager Class (1/2)  (0) 2023.04.26
Carthage 설치 및 제거  (0) 2023.01.11
NSURLSessionTask 캐싱 비활성화  (0) 2022.10.24
블로그 이미지

SKY STORY

,
반응형

NFC NDEF tag 읽기 / 쓰기 및 feliCa, iso7816, iso15693, miFare tag 읽기 관련 클래스를

만들기 위해 우선 다음과 같이 환경 설정부터 진행해 보자.

 

우선 개발자 사이트에서 App ID 설정에서 NFC 읽기 활성화 한다.

Xcode에서는 다음과 같이 적용된다.

entitlements 파일에 NDEF, TAG 등록.

Info.plist 권한 설정은 다음과 같다.

NFC 읽기 권한  설정을 한다.

<key>NFCReaderUsageDescription</key>
<string>결제를 위해 NFC 사용이 승인되어야 합니다.</string>

 

소니의 Felica tag를 읽기 위해 시스템 코드를 등록한다.

<key>com.apple.developer.nfc.readersession.felica.systemcodes</key>
<array>
	<string>88B4</string>
	<string>88A8</string>
	<string>8031</string>
	<string>88CA</string>
	<string>8FC7</string>
	<string>8E5E</string>
	<string>8E6F</string>
	<string>8F5B</string>
	<string>869F</string>
	<string>12FC</string>
	<string>0003</string>
	<string>FE00</string>
</array>
</plist>

 

iso7816 tag의 경우 다음과 같이 읽을 id를 등록한다.

<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
	<string>D2760000850101h</string>
	<string>315041592E5359532E4444463031</string>
	<string>D4100000030001</string>
	<string>325041592E5359532E4444463031</string>
	<string>44464D46412E44466172653234313031</string>
	<string>A00000000101</string>
	<string>A000000003000000</string>
	<string>A00000000300037561</string>
	<string>A00000000305076010</string>
	<string>A0000000031010</string>
	<string>A000000003101001</string>
	<string>A000000003101002</string>
	<string>A0000000032010</string>
	<string>A0000000032020</string>
	<string>A0000000033010</string>
	<string>A0000000034010</string>
	<string>A0000000035010</string>
	<string>A000000003534441</string>
	<string>A0000000035350</string>
	<string>A000000003535041</string>
	<string>A0000000036010</string>
	<string>A0000000036020</string>
	<string>A0000000038002</string>
	<string>A0000000038010</string>
	<string>A0000000039010</string>
	<string>A000000003999910</string>
	<string>A0000000040000</string>
	<string>A00000000401</string>
	<string>A0000000041010</string>
	<string>A00000000410101213</string>
	<string>A00000000410101215</string>
	<string>A0000000041010BB5449435301</string>
	<string>A0000000042010</string>
	<string>A0000000042203</string>
	<string>A0000000043010</string>
	<string>A0000000043060</string>
	<string>A000000004306001</string>
	<string>A0000000044010</string>
	<string>A0000000045010</string>
	<string>A0000000045555</string>
	<string>A0000000046000</string>
	<string>A0000000048002</string>
	<string>A0000000049999</string>
	<string>A0000000050001</string>
	<string>A0000000050002</string>
	<string>A0000000090001FF44FF1289</string>
	<string>A0000000101030</string>
	<string>A00000001800</string>
	<string>A0000000181001</string>
	<string>A000000018434D</string>
	<string>A000000018434D00</string>
	<string>A00000002401</string>
	<string>A000000025</string>
	<string>A0000000250000</string>
	<string>A00000002501</string>
	<string>A000000025010104</string>
	<string>A000000025010402</string>
	<string>A000000025010701</string>
	<string>A000000025010801</string>
	<string>A0000000291010</string>
	<string>A00000002945087510100000</string>
	<string>A00000002949034010100001</string>
	<string>A00000002949282010100000</string>
	<string>A000000029564182</string>
	<string>A00000003029057000AD13100101FF</string>
	<string>A0000000308000000000280101</string>
	<string>A0000000421010</string>
	<string>A0000000422010</string>
	<string>A0000000423010</string>
	<string>A0000000424010</string>
	<string>A0000000425010</string>
	<string>A0000000426010</string>
	<string>A00000005945430100</string>
	<string>A000000063504B43532D3135</string>
	<string>A0000000635741502D57494D</string>
	<string>A00000006510</string>
	<string>A0000000651010</string>
	<string>A00000006900</string>
	<string>A000000077010000021000000000003B</string>
	<string>A0000000790100</string>
	<string>A0000000790101</string>
	<string>A0000000790102</string>
	<string>A00000007901F0</string>
	<string>A00000007901F1</string>
	<string>A00000007901F2</string>
	<string>A0000000790200</string>
	<string>A0000000790201</string>
	<string>A00000007902FB</string>
	<string>A00000007902FD</string>
	<string>A00000007902FE</string>
	<string>A0000000790300</string>
	<string>A0000000791201</string>
	<string>A0000000791202</string>
	<string>A0000000871002FF49FF0589</string>
	<string>A00000008810200105C100</string>
	<string>A000000088102201034221</string>
	<string>A000000088102201034321</string>
	<string>A0000000960200</string>
	<string>A000000098</string>
	<string>A0000000980840</string>
	<string>A0000000980848</string>
	<string>A0000001110101</string>
	<string>A0000001110201</string>
	<string>A0000001160300</string>
	<string>A0000001166010</string>
	<string>A0000001166030</string>
	<string>A0000001169000</string>
	<string>A000000116A001</string>
	<string>A000000116DB00</string>
	<string>A000000118010000</string>
	<string>A000000118020000</string>
	<string>A000000118030000</string>
	<string>A000000118040000</string>
	<string>A0000001184543</string>
	<string>A000000118454E</string>
	<string>A0000001211010</string>
	<string>A0000001320001</string>
	<string>A0000001408001</string>
	<string>A0000001410001</string>
	<string>A0000001510000</string>
	<string>A00000015153504341534400</string>
	<string>A0000001523010</string>
	<string>A0000001524010</string>
	<string>A0000001544442</string>
	<string>A0000001570010</string>
	<string>A0000001570020</string>
	<string>A0000001570021</string>
	<string>A0000001570022</string>
	<string>A0000001570023</string>
	<string>A0000001570030</string>
	<string>A0000001570031</string>
	<string>A0000001570040</string>
	<string>A0000001570050</string>
	<string>A0000001570051</string>
	<string>A0000001570100</string>
	<string>A0000001570104</string>
	<string>A0000001570109</string>
	<string>A000000157010A</string>
	<string>A000000157010B</string>
	<string>A000000157010C</string>
	<string>A000000157010D</string>
	<string>A0000001574443</string>
	<string>A0000001574444</string>
	<string>A000000167413000FF</string>
	<string>A000000167413001</string>
	<string>A000000172950001</string>
	<string>A000000177504B43532D3135</string>
	<string>A0000001850002</string>
	<string>A0000001884443</string>
	<string>A0000002040000</string>
	<string>A0000002281010</string>
	<string>A0000002282010</string>
	<string>A00000022820101010</string>
	<string>A0000002471001</string>
	<string>A0000002472001</string>
	<string>A0000002771010</string>
	<string>A00000030600000000000000</string>
	<string>A000000308000010000100</string>
	<string>A00000031510100528</string>
	<string>A0000003156020</string>
	<string>A00000032301</string>
	<string>A0000003230101</string>
	<string>A0000003241010</string>
	<string>A000000333010101</string>
	<string>A000000333010102</string>
	<string>A000000333010103</string>
	<string>A000000333010106</string>
	<string>A000000333010108</string>
	<string>A000000337301000</string>
	<string>A000000337101000</string>
	<string>A000000337102000</string>
	<string>A000000337101001</string>
	<string>A000000337102001</string>
	<string>A000000337601001</string>
	<string>A0000003591010</string>
	<string>A0000003591010028001</string>
	<string>A00000035910100380</string>
	<string>A0000003660001</string>
	<string>A0000003660002</string>
	<string>A0000003710001</string>
	<string>A00000038410</string>
	<string>A00000038420</string>
	<string>A0000003964D66344D0002</string>
	<string>A00000039742544659</string>
	<string>A0000003974349445F0100</string>
	<string>A0000004271010</string>
	<string>A0000004320001</string>
	<string>A0000004360100</string>
	<string>A0000004391010</string>
	<string>A0000004540010</string>
	<string>A0000004540011</string>
	<string>A0000004762010</string>
	<string>A0000004763030</string>
	<string>A0000004766C</string>
	<string>A000000476A010</string>
	<string>A000000476A110</string>
	<string>A000000485</string>
	<string>A0000005241010</string>
	<string>A0000005271002</string>
	<string>A000000527200101</string>
	<string>A000000527210101</string>
	<string>A0000005591010FFFFFFFF8900000100</string>
	<string>A0000005591010FFFFFFFF8900000200</string>
	<string>A0000005591010FFFFFFFF8900000D00</string>
	<string>A0000005591010FFFFFFFF8900000E00</string>
	<string>A0000005591010FFFFFFFF8900000F00</string>
	<string>A0000005591010FFFFFFFF8900001000</string>
	<string>A00000061700</string>
	<string>A0000006200620</string>
	<string>A0000006260101010001</string>
	<string>A0000006351010</string>
	<string>A0000006581010</string>
	<string>A0000006581011</string>
	<string>A0000006582010</string>
	<string>A0000006723010</string>
	<string>A0000006723020</string>
	<string>A0000007705850</string>
	<string>A0000007790000</string>
	<string>B012345678</string>
	<string>D040000001000002</string>
	<string>D040000002000002</string>
	<string>D040000003000002</string>
	<string>D040000004000002</string>
	<string>D04000000B000002</string>
	<string>D04000000C000002</string>
	<string>D04000000D000002</string>
	<string>D040000013000001</string>
	<string>D040000013000001</string>
	<string>D040000013000002</string>
	<string>D040000013000002</string>
	<string>D040000014000001</string>
	<string>D040000015000001</string>
	<string>D040000015000001</string>
	<string>D0400000190001</string>
	<string>D0400000190002</string>
	<string>D0400000190003</string>
	<string>D0400000190004</string>
	<string>D0400000190010</string>
	<string>D268000001</string>
	<string>D276000005</string>
	<string>D276000005AA040360010410</string>
	<string>D276000005AA0503E00401</string>
	<string>D276000005AA0503E00501</string>
	<string>D276000005AA0503E0050101</string>
	<string>D276000005AB0503E0040101</string>
	<string>D27600002200000001</string>
	<string>D27600002200000002</string>
	<string>D27600002200000060</string>
	<string>D276000025</string>
	<string>D27600002545410100</string>
	<string>D27600002545500100</string>
	<string>D27600002547410100</string>
	<string>D276000060</string>
	<string>D2760000850100</string>
	<string>D2760000850101</string>
	<string>D276000118</string>
	<string>D2760001180101</string>
	<string>D27600012401</string>
	<string>D276000124010101FFFF000000010000</string>
	<string>D2760001240102000000000000010000</string>
	<string>D27600012402</string>
	<string>D2760001240200010000000000000000</string>
	<string>D4100000011010</string>
	<string>D5280050218002</string>
	<string>D5780000021010</string>
	<string>D7560000010101</string>
	<string>D7560000300101</string>
	<string>D8040000013010</string>
	<string>E80704007F00070302</string>
	<string>E82881C11702</string>
	<string>E828BD080F</string>
	<string>F0000000030001</string>
</array>
</plist>

 

 

2023.04.26 - [개발노트] - NFC tag read/write Manager Class (1/2)

2023.04.26 - [분류 전체보기] - NFC tag read/write Manager Class (2/2)

 

반응형

'개발 > iOS' 카테고리의 다른 글

Rosetta2  (0) 2023.04.26
NFC tag read/write Manager Class (2/2)  (0) 2023.04.26
Carthage 설치 및 제거  (0) 2023.01.11
NSURLSessionTask 캐싱 비활성화  (0) 2022.10.24
UIScrollView 스크린샷 만들기  (0) 2022.10.24
블로그 이미지

SKY STORY

,
반응형

https://github.com/Carthage/Carthage

 

 

Homebrew 설치 (설치되어 있지 않을 경우)

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null

 

Carthage 설치

brew install Carthage

 

Carthage 업그레이드

brew upgrade Carthage

 

Carthage 제거

방법 1 (Carthage.pkg로 설치했을 경우) :

    rm -rf /usr/local/bin/carthage

    sudo rm -rf /Library/Frameworks/CarthageKit.framework

방법 2 (Homebrew로 설치했을 경우) :

    brew uninstall --force carthage

 

반응형

'개발 > iOS' 카테고리의 다른 글

NFC tag read/write Manager Class (2/2)  (0) 2023.04.26
NFC tag read/write Manager Class (1/2)  (0) 2023.04.26
NSURLSessionTask 캐싱 비활성화  (0) 2022.10.24
UIScrollView 스크린샷 만들기  (0) 2022.10.24
문자열 숫자에 콤마 넣기  (0) 2022.10.14
블로그 이미지

SKY STORY

,
반응형

NSURLSessionTask 를 사용하여 파일을 로딩할 경우
최초 1회 이후 캐싱되어 웹에서 json파일을 변경해도 반영되지 않는다.
그래서 다음과같이 캐싱을 비활성화 하여 파일 변경사항이 즉시 반영되도록 하였다.

// 테스트용 json파일 다운로드
let urlString = "https://www.test.com/data/info.json"

// NSURLSessionTask 캐싱 비활성화
let config = URLSessionConfiguration.default
config.requestCachePolicy = .reloadIgnoringLocalCacheData
config.urlCache = nil
let session = URLSession(configuration: config)

session.dataTask(with: URL(string: urlString)!) { (data, response, error) -> Void in
    if error == nil && data != nil {
        do {
            let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String: Any]
            print("\(json)")
	
        } catch {
            print("Something went wrong")
        }
    }
}.resume()

 

반응형
블로그 이미지

SKY STORY

,
반응형

네이티브 UIScrollView 화면에서 스크린샷 구하는 방법에 대해 알아보자.

일반적으로 스크린샷을 구해보면 화면에 보이는 부분만

구해지며 숨겨진 영역은 흰색화면으로 출력된다. (iOS 13.0이상 에서 생기는 버그)

extension UIScrollView {
    func screenshot() -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(contentSize, false, 0.0)
        let savedContentOffset = contentOffset
        let savedFrame = frame
        defer {
            UIGraphicsEndImageContext()
            contentOffset = savedContentOffset
            frame = savedFrame
        }
        contentOffset = .zero
        frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)
        guard let ctx = UIGraphicsGetCurrentContext() else {
            return nil
        }
        layer.render(in: ctx)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        return image
    }
}

이를 위해 UIScrollView를 부모로 부터 Layout 컨텐츠를 비활성하고 스크린샷 이미지를 구하고 다시

원래대로 활성화 시켜주는 방법으로 해결 가능하다.

extension UIScrollView {
    func screenshot() -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(contentSize, false, 0.0)
        let savedContentOffset = contentOffset
        let actualConstraints = relatedConstraints()
        NSLayoutConstraint.deactivate(actualConstraints)
        translatesAutoresizingMaskIntoConstraints = true
        frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)
        contentOffset = .zero
        defer {
            UIGraphicsEndImageContext()
            translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate(actualConstraints)
            superview?.layoutIfNeeded()
            contentOffset = savedContentOffset
        }
        guard let ctx = UIGraphicsGetCurrentContext() else {
            return nil
        }
        layer.render(in: ctx)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        return image
    }
}


extension UIView {
    func relatedConstraints() -> [NSLayoutConstraint] {
        var constraints = self.constraints
        var parent = superview
        while parent != nil {
            constraints.append(contentsOf: parent!.constraints.filter { $0.firstItem === self || $0.secondItem === self })
            parent = parent!.superview
        }
        return constraints
    }
    
    class func getAllSubviews<T: UIView>(from parenView: UIView) -> [T] {
        return parenView.subviews.flatMap { subView -> [T] in
            var result = getAllSubviews(from: subView) as [T]
            if let view = subView as? T { result.append(view) }
            return result
        }
    }
	
    class func getAllSubviews(from parenView: UIView, types: [UIView.Type]) -> [UIView] {
        return parenView.subviews.flatMap { subView -> [UIView] in
            var result = getAllSubviews(from: subView) as [UIView]
            for type in types {
                if subView.classForCoder == type {
                    result.append(subView)
                    return result
                }
            }
            return result
        }
    }
	
    func getAllSubviews<T: UIView>() -> [T] { return UIView.getAllSubviews(from: self) as [T] }
    func get<T: UIView>(all type: T.Type) -> [T] { return UIView.getAllSubviews(from: self) as [T] }
    func get(all types: [UIView.Type]) -> [UIView] { return UIView.getAllSubviews(from: self, types: types) }
}


/*
// 현재 뷰의 하위뷰 중 UIScrollView를 구한다.
if let scrollView = self.view.get(all: UIScrollView.self).first {
    // 스크롤뷰의 모든 컨텐츠가 나오도록 스크린샷 이미지로 반환한다.
    let image = scrollView.screenshot()
    
    // 주어진 이미지를 공유
    DispatchQueue.main.async {
        let imageToShare = [ image ]
        let activityVC = UIActivityViewController(activityItems: imageToShare as [Any], applicationActivities: nil)
        self.present(activityVC, animated: true, completion: nil)
    }
}
*/

 

반응형
블로그 이미지

SKY STORY

,
반응형

문자열 숫자에 3자리마다 콤마를 넣을 때 사용할 함수를 알아보자

extension String {
    var commas: String {
        guard self.isEmpty == false else { return "" }
        // 이미 ','가 존재하면 제거 
        let str = replacingOccurrences(of: ",", with: "", options: .literal, range: nil)
        
        let numFmt = NumberFormatter()
        numFmt.numberStyle = .decimal
        // 포멧 적용 소수점 이하 자릿수
        // 소수점 이하 최대 7자리까지만 적용되며 반올림 된다.
        numFmt.maximumFractionDigits = 7
        // 현재 문자열 숫자를 NSNumber객체로 변환
        let num = numFmt.number(from: str)
        // 3자릿수마다 콤마 삽입
        return numFmt.string(for: num) ?? str
    }
}

/*
let s = "1234567890.123456789".commas
print("\(s)")

let s2 = "1234567890".commas
print("\(s2)")

결과 : 
1,234,567,890.1234567
1,234,567,890
*/

소수점 이하 7자리까지만 지원된다는 문제가 있지만 일반적으로 사용하는데 문제없을 듯하다. 

maximumFractionDigits 참조 :

https://developer.apple.com/documentation/foundation/numberformatter/1415364-maximumfractiondigits

 

만약 소수점 이하 자릿수 제한 없이 구현해야 한다면 다음 함수를 이용하자.

extension String {
	var commas : String {
        guard self.isEmpty == false else { return "" }
        let str = replacingOccurrences(of: ",", with: "", options: .literal, range: nil)
        // 소수점을 기준으로 토큰 분리
        let strArr = str.components(separatedBy: ".")
        guard strArr.count > 0 else { return self }
        guard strArr[0].isNumber else { return self }
        let numFmt = NumberFormatter()
        numFmt.numberStyle = .decimal
        let num = numFmt.number(from: strArr[0])
        var numStr = numFmt.string(from: num) ?? strArr[0]
        if strArr[1].isEmpty == false {
            numStr.append(".\(strArr[1])")
        }
        return numStr
    }
}

/*
let s = "1234567890.123456789".commas
print("\(s)")

결과 : 
1,234,567,890.123456789
*/

소수점 이하 자릿수 제한 없이 사용 가능하다.

 

반응형
블로그 이미지

SKY STORY

,
반응형

유니버셜링크 적용 시 도메인 루트에 AASA(apple-app-site-association)파일을 생성하여
관련정보를 설정해야 한다. 개발 중에 주의할 점은 이 파일을 다운받게 되면 캐싱되어
그 이후 계속적으로 캐싱된 파일이 사용되므로 중간에 내용을 바꾸어도 적용되지 않는다.
이 상황을 모르고 작업하다보면 원인파악에 많은 시간을 낭비할 수 있으므로 다음 내용을 숙지하기 바란다.

AASA파일을 다운받아 캐싱되는 경우
1. 앱 최초 설치 시 AASA파일 인스톨할 경우 (앱스토어 다운로드)
2. 앱 스토어 업데이트로 인한 인스톨 (앱 업데이트)

위 두가지 이외는 더 이상 다운로드 되지 않을 수 있으므로 중간에 해당 파일을 수정했을 경우
정상동작하지 않고 이전 파일(캐싱된 파일)로 적용된다.
또한 AASA파일을 다운로드가 안될경우(방화벽 또는 도메인 문제 등.) 역시 정상 동작되지 않으므로
해당 파일 다운로드가 가능한지 체크한다.
개발중 AASA파일 갱신으로 인하여 캐싱된 파일을 강제 업데이트 할 경우 다음과정을 통해 업데이트하길 바란다.

1. 디바이스에서 앱 제거
2. 디바이스 재시작
3. 앱 재설치
4. 1~3번을 2~3번 재시도
5. 앱 내에서 AASA파일이 다운로드 성공인지 확인.

AASA 파일 다운로드 성공 확인 방법 :
- 휴대폰의 노트앱 실행
- 링크를 노트에 붙여넣기
- 해당 링크를 2초이상 누르고 있으면 드롭 다운 메뉴가 뜨는데 
  메뉴리스트에 '링크 열기' 표시되지 않고 '[앱이름] 으로 열기'가 
  표시되면 성공이다.
  
  ex) AASA 파일 링크
  https://myapp.page.link/apple-app-site-association

  ex) AASA 파일 내용 
  {"applinks":{"apps":[],"details":[{"appID":"ABCDEF2TH3.com.company.myapp","paths":["NOT /_/*","/*"]}]}}



반응형

'개발 > iOS' 카테고리의 다른 글

UIScrollView 스크린샷 만들기  (0) 2022.10.24
문자열 숫자에 콤마 넣기  (0) 2022.10.14
웹 저장 데이터 (Cookie, Cache) 삭제  (0) 2021.09.24
버전분기 샘플  (0) 2021.08.02
시뮬레이터 분기  (0) 2021.08.02
블로그 이미지

SKY STORY

,
반응형

하이브리드 앱을 제작하다 보면 웹페이지에서 저장한 데이터(쿠키, 캐시)를

삭제해야 하는 경우가 생긴다.

하지만 모든 저장 데이터를 삭제하다 보면 다른 웹 페이지에 저장된 데이터까지

모두 삭제되어 예외사항이 발생할 수 있다.

 

특정 페이지에서 저장된 데이터만 삭제하는 코드를 알아보자.

// 다음과 같이 displayName을 이용하여 특정 데이터만 삭제.
WKWebsiteDataStore.default().fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in
	WKWebsiteDataStore.default().removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(),
                                            for: records.filter { $0.displayName.contains("mytest.com") },
                                            completionHandler: {
                                                print("\("delete website data")")
                                            })
}

 

다음은 모든 캐시데이터 및 특정 도메인 쿠키 삭제

// 모든 캐시데이터 삭제
let cacheDataTypes : Set<String> = [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache]
let date = Date(timeIntervalSince1970: 0)
WKWebsiteDataStore.default().removeData(ofTypes: cacheDataTypes,
                                        modifiedSince: date ,
                                        completionHandler: {
                                            print("delete cache data")
                                        })
                                        
                                        
                                        
// 특정 도메인 쿠키만 삭제
let cookieDataType : Set<String> = [WKWebsiteDataTypeCookies]
WKWebsiteDataStore.default().fetchDataRecords(ofTypes: cookieDataType) { records in
    WKWebsiteDataStore.default().removeData(ofTypes: cookieDataType,
                                            for: records.filter { $0.displayName.contains("mytest.com") },
                                            completionHandler: {
                                                print("delete cookie data")
                                            })
}

 

웹 저장 데이터 로그 출력 

// 쿠키 정보 보기 
WKWebsiteDataStore.default().httpCookieStore.getAllCookies { cookies in
    for cookie in cookies {
        print("cookie : \(cookie)")
    }
}



/* 
출력 로그 :
cookie : <NSHTTPCookie
	version:1
	name:JSESSIONID
	value:43C2101193D26FDE7F2DB61D86E15B0E
	expiresDate:'(null)'
	created:'2021-09-17 00:06:31 +0000'
	sessionOnly:TRUE
	domain:www.mytest.com
	partition:none
	sameSite:none
	path:/
	isSecure:FALSE
	isHTTPOnly: YES
 path:"/" isSecure:FALSE isHTTPOnly: YES>
 */

 

 

 

 

반응형

'개발 > iOS' 카테고리의 다른 글

문자열 숫자에 콤마 넣기  (0) 2022.10.14
유니버셜 링크(Universal Links) 문제점 해결  (0) 2022.07.21
버전분기 샘플  (0) 2021.08.02
시뮬레이터 분기  (0) 2021.08.02
Foreground 이벤트 받기  (0) 2021.08.02
블로그 이미지

SKY STORY

,