I have a app that has a UILabel that I would like to be updated by another swift class? The class is a service class and has no relation to the view controller with the UILabel but I would still like that service class to be able to update the label.
I think that this answer was what I needed but it does not work for Swift 3 and the text the label changes to is hardcoded which is not what I wanted either. I could not find anything else helpful about this.
BTService.swift:
import Foundation
import CoreBluetooth
let BLEServiceUUID = CBUUID(string: "025A7775-49AA-42BD-BBDB-E2AE77782966")
let PositionCharUUID = CBUUID(string: "A9CD2F86-8661-4EB1-B132-367A3434BC90") //RX let
let WriteCharUUID = CBUUID(string: "F38A2C23-BC54-40FC-BED0-60EDDA139F47") //TX let F38A2C23-BC54-40FC-BED0-60EDDA139F47
let BLEServiceChangedStatusNotification = "kBLEServiceChangedStatusNotification"
class BTService: NSObject, CBPeripheralDelegate {
var peripheral: CBPeripheral?
var positionCharacteristic: CBCharacteristic?
var writeCharacteristic: CBCharacteristic?
init(initWithPeripheral peripheral: CBPeripheral) {
super.init()
self.peripheral = peripheral
self.peripheral?.delegate = self
}
deinit{
self.reset()
}
func startDiscoveringServices() {
self.peripheral?.discoverServices([BLEServiceUUID])
}
func reset() {
if peripheral != nil {
peripheral = nil
}
// Deallocating therefore send notification
self.sendBTServiceNotificationWithIsBluetoothConnected(false)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
let uuidsForBTService: [CBUUID] = [PositionCharUUID]
let uuidsForBTService1: [CBUUID] = [WriteCharUUID]
if (peripheral != self.peripheral) { // Wrong Peripheral
return
}
if (error != nil) {
return
}
if ((peripheral.services == nil) || (peripheral.services?.count == 0)) {
// No Services
return
}
for service in peripheral.services! {
if service.uuid == BLEServiceUUID {
peripheral.discoverCharacteristics(uuidsForBTService, for: service
)
peripheral.discoverCharacteristics(uuidsForBTService1, for: service )
} }
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
if (peripheral != self.peripheral) { // Wrong Peripheral
return }
if (error != nil) {
return
}
for characteristic in service.characteristics! { if characteristic.uuid == PositionCharUUID {
self.positionCharacteristic = (characteristic )
peripheral.setNotifyValue(true, for: characteristic )
self.sendBTServiceNotificationWithIsBluetoothConnected(true)
}
else if characteristic.uuid == WriteCharUUID {
self.writeCharacteristic = (characteristic )
peripheral.setNotifyValue(true, for: characteristic )
self.sendBTServiceNotificationWithIsBluetoothConnected(true)
}
}
}
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?){
let it = NSString(data: characteristic.value!, encoding: String.Encoding.utf8.rawValue)
if(it != nil){
print("Val: \(it)") //Stackoverflow: This is what needs to be sent to the ViewController to be updated!!!
}
}
func writePosition(position: UInt8) {
if self.writeCharacteristic == nil {
return
}
var positionValue = position
let data = NSData(bytes: &positionValue, length: MemoryLayout<UInt8>.size)
self.peripheral?.writeValue(data as Data, for: self.writeCharacteristic!, type: CBCharacteristicWriteType.withResponse)
}
func readPosition() -> NSString? {
if self.positionCharacteristic == nil {
return nil }
self.peripheral?.readValue(for: self.positionCharacteristic!)
if ((self.positionCharacteristic?.value) != nil) {
print(self.positionCharacteristic!.value!)
return NSString(data: self.positionCharacteristic!.value!, encoding:
String.Encoding.utf8.rawValue) }
return nil
}
func sendBTServiceNotificationWithIsBluetoothConnected(_ isBluetoothConnected: Bool) {
let connectionDetails = ["isConnected": isBluetoothConnected]
NotificationCenter.default.post(name: Notification.Name(rawValue: BLEServiceChangedStatusNotification), object: self, userInfo: connectionDetails)
}
}
The ViewController:
import UIKit
import AVFoundation
class ViewController: UIViewController {
#IBOutlet weak var imgBluetoothStatus: UIImageView!
#IBOutlet weak var positionSlider: UISlider!
#IBOutlet weak var BottomLabel: UILabel!
var timerTXDelay: Timer?
var allowTX = true
var lastPosition: UInt8 = 255
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Rotate slider to vertical position
// Watch Bluetooth connection
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.connectionChanged(_:)), name: NSNotification.Name(rawValue: BLEServiceChangedStatusNotification), object: nil)
// Start the Bluetooth discovery process
_ = btDiscoverySharedInstance
}
deinit {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: BLEServiceChangedStatusNotification), object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.stopTimerTXDelay()
}
#IBAction func positionSliderChanged(_ sender: UISlider) {
self.sendPosition(UInt8(sender.value))
}
func connectionChanged(_ notification: Notification) {
// Connection status changed. Indicate on GUI.
let userInfo = (notification as NSNotification).userInfo as! [String: Bool]
DispatchQueue.main.async(execute: {
// Set image based on connection status
if let isConnected: Bool = userInfo["isConnected"] {
if isConnected {
self.imgBluetoothStatus.image = UIImage(named: "Bluetooth_Connected")
// Send current slider position
self.sendPosition(UInt8( self.positionSlider.value))
} else {
self.imgBluetoothStatus.image = UIImage(named: "Bluetooth_Disconnected")
}
}
});
}
func sendPosition(_ position: UInt8) {
// Valid position range: 0 to 180
if !allowTX {
return
}
// Validate value
if position == lastPosition {
return
}
else if ((position < 0) || (position > 180)) {
return
}
// Send position to BLE Shield (if service exists and is connected)
if let bleService = btDiscoverySharedInstance.bleService {
bleService.writePosition(position: position)
lastPosition = position;
// Start delay timer
allowTX = false
if timerTXDelay == nil {
timerTXDelay = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(ViewController.timerTXDelayElapsed), userInfo: nil, repeats: false)
}
}
}
func timerTXDelayElapsed() {
self.allowTX = true
self.stopTimerTXDelay()
// Send current slider position
self.sendPosition(UInt8(self.positionSlider.value))
}
func stopTimerTXDelay() {
if self.timerTXDelay == nil {
return
}
timerTXDelay?.invalidate()
self.timerTXDelay = nil
}
}
In the BTService, there is a didUpdateValueFor function and I would like the it variable to be sent to the ViewController and have it set as the BottomLabel.
it should be similar to your existing BLEServiceChangedStatusNotification:
define the name:
let BLEServicePeripheralChangedNotification = "kBLEServicePeripheralChangedNotification"
add the observer in viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.peripheralChanged(_:)), name: NSNotification.Name(rawValue: BLEServicePeripheralChangedNotification), object: nil)
remove the observer in deinit:
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: BLEServicePeripheralChangedNotification), object: nil)
add the function:
func peripheralChanged(_ notification: Notification) {
// peripheral status changed. Indicate on GUI.
let userInfo = (notification as NSNotification).userInfo as! [String: NSString]
if let it: NSString = userInfo["it"] {
//BottomLabel should be bottomLabel in declaration!
BottomLabel.text = it
}
}
add the post to notification center:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?){
let it = NSString(data: characteristic.value!, encoding: String.Encoding.utf8.rawValue)
if(it != nil){
print("Val: \(it)")
let itDetails = ["it": it]
NotificationCenter.default.post(name: Notification.Name(rawValue: BLEServicePeripheralChangedNotification), object: self, userInfo: itDetails)
}
}
PS: BottomLabel should be bottomLabel in declaration.
Related
This is my first experience with creating purchases. The app I'm working on hasn't been released yet. I've been testing subscriptions locally using the Configuration.storekit file. Everything worked fine.
I recently encountered a problem - my subscriptions are no longer displayed in the project. I got an error like this in the terminal:
UPD:
I decided to check the application on the emulator and everything works there. As far as I remember everything broke after installing xcode 14 and updating to ios 16.
On the physical device, the problem remains.
I didn't change the code in those places. I tried to create new .storekit files, but it still doesn't work.
I tried to load the .storekit file with the synchronization. In it the price is pulled up and displayed correctly, as on the site, but in the terminal again writes the same error.
Here is the file that works with purchases:
import StoreKit
typealias RequestProductsResult = Result<[SKProduct], Error>
typealias PurchaseProductResult = Result<Bool, Error>
typealias RequestProductsCompletion = (RequestProductsResult) -> Void
typealias PurchaseProductCompletion = (PurchaseProductResult) -> Void
class Purchases: NSObject {
static let `default` = Purchases()
private let productIdentifiers = Set<String>(
arrayLiteral: "test.1month", "test.6month", "test.12month"
)
private var products: [String: SKProduct]?
private var productRequest: SKProductsRequest?
private var productsRequestCallbacks = [RequestProductsCompletion]()
fileprivate var productPurchaseCallback: ((PurchaseProductResult) -> Void)?
func initialize(completion: #escaping RequestProductsCompletion) {
requestProducts(completion: completion)
}
private func requestProducts(completion: #escaping RequestProductsCompletion) {
guard productsRequestCallbacks.isEmpty else {
productsRequestCallbacks.append(completion)
return
}
productsRequestCallbacks.append(completion)
let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
productRequest.delegate = self
productRequest.start()
self.productRequest = productRequest
}
func purchaseProduct(productId: String, completion: #escaping (PurchaseProductResult) -> Void) {
guard productPurchaseCallback == nil else {
completion(.failure(PurchasesError.purchaseInProgress))
return
}
guard let product = products?[productId] else {
completion(.failure(PurchasesError.productNotFound))
return
}
productPurchaseCallback = completion
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
public func restorePurchases(completion: #escaping (PurchaseProductResult) -> Void) {
guard productPurchaseCallback == nil else {
completion(.failure(PurchasesError.purchaseInProgress))
return
}
productPurchaseCallback = completion
SKPaymentQueue.default().restoreCompletedTransactions()
}
}
extension Purchases: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
guard !response.products.isEmpty else {
print("Found 0 products")
productsRequestCallbacks.forEach { $0(.success(response.products)) }
productsRequestCallbacks.removeAll()
return
}
var products = [String: SKProduct]()
for skProduct in response.products {
print("Found product: \(skProduct.productIdentifier)")
products[skProduct.productIdentifier] = skProduct
}
self.products = products
productsRequestCallbacks.forEach { $0(.success(response.products)) }
productsRequestCallbacks.removeAll()
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("Failed to load products with error:\n \(error)")
productsRequestCallbacks.forEach { $0(.failure(error)) }
productsRequestCallbacks.removeAll()
}
}
extension Purchases: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased, .restored:
if finishTransaction(transaction) {
SKPaymentQueue.default().finishTransaction(transaction)
productPurchaseCallback?(.success(true))
UserDefaults.setValue(true, forKey: "isPurchasedSubscription")
} else {
productPurchaseCallback?(.failure(PurchasesError.unknown))
}
case .failed:
productPurchaseCallback?(.failure(transaction.error ?? PurchasesError.unknown))
SKPaymentQueue.default().finishTransaction(transaction)
default:
break
}
}
productPurchaseCallback = nil
}
}
extension Purchases {
func finishTransaction(_ transaction: SKPaymentTransaction) -> Bool {
let productId = transaction.payment.productIdentifier
print("Product \(productId) successfully purchased")
return true
}
}
There is also a file that is responsible for displaying available subscription options:
//
// PremiumRatesTVC.swift
// CalcYou
//
// Created by Admin on 29.08.2022.
//
import StoreKit
import UIKit
class PremiumRatesTVC: UITableViewController {
var oneMonthPrice = ""
var sixMonthPrice = ""
var twelveMonthPrice = ""
#IBOutlet weak var oneMonthPriceLabel: UILabel!
#IBOutlet weak var oneMothDailyPriceLabel: UILabel!
#IBOutlet weak var sixMonthPriceLabel: UILabel!
#IBOutlet weak var sixMonthDailyPriceLabel: UILabel!
#IBOutlet weak var twelveMonthPriceLabel: UILabel!
#IBOutlet weak var twelveMonthDailyPriceLabel: UILabel!
#IBOutlet weak var tableViewCellOneMonth: UITableViewCell!
#IBOutlet weak var tableViewCellSixMonth: UITableViewCell!
#IBOutlet weak var tableViewCellTwelveMonth: UITableViewCell!
#IBAction func cancelButton(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
// MARK: ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
hideSubscriptions()
navigationItem.title = "Premium PRO version"
Purchases.default.initialize { [weak self] result in
guard let self = self else { return }
switch result {
case let .success(products):
guard products.count > 0 else {
let message = "Failed to get a list of subscriptions. Please try again later."
self.showMessage("Oops", withMessage: message)
return
}
self.showSubscriptions()
DispatchQueue.main.async {
self.updateInterface(products: products)
}
default:
break
}
}
}
// MARK: Functions()
private func updateInterface(products: [SKProduct]) {
updateOneMonth(with: products[0])
updateSixMonth(with: products[1])
updateTwelveMonth(with: products[2])
}
private func hideSubscriptions() {
DispatchQueue.main.async {
self.tableViewCellOneMonth.isHidden = true
self.tableViewCellSixMonth.isHidden = true
self.tableViewCellTwelveMonth.isHidden = true
}
}
private func showSubscriptions() {
DispatchQueue.main.async {
self.tableViewCellOneMonth.isHidden = false
self.tableViewCellSixMonth.isHidden = false
self.tableViewCellTwelveMonth.isHidden = false
}
}
func showMessage(_ title: String, withMessage message: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title,
message: message,
preferredStyle: UIAlertController.Style.alert)
let dismiss = UIAlertAction(title: "Ok",
style: UIAlertAction.Style.default,
handler: nil)
alert.addAction(dismiss)
self.present(alert, animated: true, completion: nil)
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if indexPath.section == 0 && indexPath.row == 0 {
guard let premiumBuyVC = storyboard.instantiateViewController(identifier: "PremiumBuyVC") as? PremiumBuyVC else { return }
premiumBuyVC.price = oneMonthPrice
premiumBuyVC.productId = "1month"
premiumBuyVC.period = "per month"
show(premiumBuyVC, sender: nil)
}
if indexPath.section == 1 && indexPath.row == 0 {
guard let premiumBuyVC = storyboard.instantiateViewController(identifier: "PremiumBuyVC") as? PremiumBuyVC else { return }
premiumBuyVC.price = sixMonthPrice
premiumBuyVC.productId = "6month"
premiumBuyVC.period = "per 6 month"
show(premiumBuyVC, sender: nil)
}
if indexPath.section == 2 && indexPath.row == 0 {
guard let premiumBuyVC = storyboard.instantiateViewController(identifier: "PremiumBuyVC") as? PremiumBuyVC else { return }
premiumBuyVC.price = twelveMonthPrice
premiumBuyVC.productId = "12month"
premiumBuyVC.period = "per 12 month"
show(premiumBuyVC, sender: nil)
}
}
}
extension SKProduct {
public var localizedPrice: String? {
let numberFormatter = NumberFormatter()
numberFormatter.locale = self.priceLocale
numberFormatter.numberStyle = .currency
return numberFormatter.string(from: self.price)
}
}
// MARK: Обновление информации
// в cell для 1, 6, 12 месяцев
extension PremiumRatesTVC {
func updateOneMonth(with product: SKProduct) {
let withCurrency = "\(product.priceLocale.currencyCode ?? " ")"
let daily = dailyPrice(from: Double(truncating: product.price), withMonth: 1.0)
oneMonthPriceLabel.text = "\(product.price) \(withCurrency)"
oneMothDailyPriceLabel.text = "\(daily) \(withCurrency)"
oneMonthPrice = "\(product.price) \(withCurrency)"
}
func updateSixMonth(with product: SKProduct) {
let withCurrency = "\(product.priceLocale.currencyCode ?? " ")"
let daily = dailyPrice(from: Double(truncating: product.price), withMonth: 6.0)
sixMonthPriceLabel.text = "\(product.price) \(withCurrency)"
sixMonthDailyPriceLabel.text = "\(daily) \(withCurrency)"
sixMonthPrice = "\(product.price) \(withCurrency)"
}
func updateTwelveMonth(with product: SKProduct) {
let withCurrency = "\(product.priceLocale.currencyCode ?? " ")"
let daily = dailyPrice(from: Double(truncating: product.price), withMonth: 12.0)
twelveMonthPriceLabel.text = "\(product.price) \(withCurrency)"
twelveMonthDailyPriceLabel.text = "\(daily) \(withCurrency)"
twelveMonthPrice = "\(product.price) \(withCurrency)"
}
func dailyPrice(from value: Double, withMonth: Double) -> String {
let days = withMonth * 30
let result = value / days
return String(format: "%.2f", result)
}
}
This image shows the testConfiguration.storekit file:
Also the image from the edit scheme:
also the file testConfiguration.storekit in the left menu with a question mark.
I hope I described the problem I encountered in detail and correctly. Many thanks to everyone who took the time.
I had this problem too. Try with a device on iOS 15.X.
Built with Xcode 14.0.1 iPhone 13 iOS 16.0: Skipping product because no price was available
Built with Xcode 14.0.1 iPhone 11 iOS 15.5: everything works.
I had the same problem and the same answers as #Vjardel, that this occurs on iOS 16 when started with Xcode. In my case I tested it with an iPad mini 5th generation on iOS 16 Beta 10.
Although, I discovered that this issues does not happen on the same device, if you try it with a TestFlight build. Therefore, you can test it with TestFlight, plus I assume that if the app is in the App Store the issue won't happen, as well.
My boss didn't have the Paid Apps field filled in. Be sure to look to make sure it is active.
Check this answer
I am using google place api to get location in my swift application. In my app when i am going to place api screen by clicking on the textfield present inside the cell of my tableview it is working fine. But when i am using the same code to show the place api screen by clicking on the textfield present inside the uiview, after selecting any place it is comming back to the previous screen, but once again the place api screen opening automatically. i have added my code, can anyonce see it and let me know the solution?
import GooglePlaces
class AddPatientViewController: UIViewController{
//MARK:- Properties
#IBOutlet weak var location_TextField: UITextField!
//MARK:- Variables
var locationManager = CLLocationManager()
var mAppDelegate:AppDelegate = UIApplication.shared.delegate as! AppDelegate;
//MARK:- LifeCycles Methods
override func viewDidLoad() {
super.viewDidLoad()
setDelegate()
}
//MARK:- Helpers
private func setDelegate(){
location_TextField.delegate = self
}
//MARK: - getLocation
func getCurrentLatLongFromCLlocation() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
switch CLLocationManager.authorizationStatus() {
case .notDetermined, .restricted, .denied:
print("No access")
getPermissionAgain()
case .authorizedAlways, .authorizedWhenInUse:
print("Access")
if let coordinateValue = locationManager.location?.coordinate {
mAppDelegate.cordinateVal = coordinateValue
self.getPlaceDataUsingLocation()
}
#unknown default:
getPermissionAgain()
}
} else {
print("Location services are not enabled")
getPermissionAgain()
}
}
func getPermissionAgain(){
let alert = UIAlertController(title: "Simplr", message: "Required location permission to set your search location", preferredStyle: .alert)
let okayAction = UIAlertAction(title: "Dismiss", style: .default, handler: nil)
let settingsAction = UIAlertAction(title: "Settings", style: .default) { (action) in
if let appSettings = URL(string: UIApplication.openSettingsURLString) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(appSettings, options: [:], completionHandler: { (enabled) in
DispatchQueue.main.async {
//UIApplication.shared.registerForRemoteNotifications()
}
})
} else {
// Fallback on earlier versions
}
}
}
alert.addAction(okayAction)
alert.addAction(settingsAction)
present(alert, animated: false, completion: nil)
}
func getPlaceDataUsingLocation(){
DispatchQueue.global().async {
// Specify the place data types to return.
let placesClient = GMSPlacesClient.init()
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue) | UInt(GMSPlaceField.coordinate.rawValue) | UInt(GMSPlaceField.formattedAddress.rawValue))
placesClient.currentPlace { (list, error) in
if let error = error {
print("An error occurred: \(error.localizedDescription)")
return
}
if let placeLikelihoodList = list?.likelihoods {
for likelihood in placeLikelihoodList {
let place = likelihood.place
print("\n\n")
print("Current Place name \(String(describing: place.name)) at likelihood \(likelihood.likelihood)")
print("Current PlaceID \(String(describing: place.placeID))")
print("Formatted Address: \(String(describing: place.formattedAddress))")
// print("Fields: \(place.)")
print("\nAddress Component: \(String(describing: place.addressComponents))\n")
print("Type: \(String(describing: place.types))")
}
}
}
}
}
}
//MARK: - UITextfield Delegate Methods
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool{
if textField == location_TextField{
let autocompleteController = GMSAutocompleteViewController()
autocompleteController.delegate = self
// autocompleteController.primaryTextColor = UIColor(hex: "#4C2B6D")
// autocompleteController.secondaryTextColor = UIColor(hex: "#4C2B6D")
// Specify the place data types to return.
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue) | UInt(GMSPlaceField.coordinate.rawValue) |
UInt(GMSPlaceField.formattedAddress.rawValue) | UInt(GMSPlaceField.addressComponents.rawValue))
autocompleteController.placeFields = fields
autocompleteController.navigationController?.navigationBar.barTintColor = .blue
autocompleteController.navigationController?.navigationBar.prefersLargeTitles = true
// Specify a filter.
let filter = GMSAutocompleteFilter()
filter.type = .geocode
autocompleteController.autocompleteFilter = filter
// Display the autocomplete view controller.
autocompleteController.modalPresentationStyle = .fullScreen
present(autocompleteController, animated: true, completion: nil)
}
return true
}
}
//MARK: - GMSAutocompleteViewControllerDelegate
#available(iOS 13.0, *)
extension AddPatientViewController: GMSAutocompleteViewControllerDelegate {
// Handle the user's selection.
func viewController(_ viewController: GMSAutocompleteViewController, didAutocompleteWith place: GMSPlace) {
print("selected place is \(getFormattedAddressFromAddressComponents(place: place))")
location_TextField.text = getFormattedAddressFromAddressComponents(place: place)
self.dismiss(animated: true)
}
func viewController(_ viewController: GMSAutocompleteViewController, didFailAutocompleteWithError error: Error) {
// TODO: handle the error.
print("Error: ", error.localizedDescription)
}
// User canceled the operation.
func wasCancelled(_ viewController: GMSAutocompleteViewController) {
self.dismiss(animated: true)
}
// Turn the network activity indicator on and off again.
func didRequestAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func didUpdateAutocompletePredictions(_ viewController: GMSAutocompleteViewController) {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
func getFormattedAddressFromAddressComponents(place: GMSPlace) -> String{
var neighborhood = ""
var locality = ""
var sublocality = ""
var administrative_area_level_1 = ""
var formatedAddress = ""
if let addressComponents : Array<GMSAddressComponent> = place.addressComponents{
print(addressComponents)
for component in addressComponents{
if component.types.contains("administrative_area_level_1"){
if let shortName = component.shortName{
administrative_area_level_1 = shortName
}
}
else if component.types.contains("neighborhood")
{
neighborhood = component.name
} else if component.types.contains("sublocality")
{
sublocality = component.name
}else if component.types.contains("locality")
{
locality = component.name
}
}
var shouldAddComma = false
if !isNullObject(anyObject: administrative_area_level_1){
shouldAddComma = true
}
if !isNullObject(anyObject: neighborhood){
formatedAddress = neighborhood
}else if !isNullObject(anyObject: sublocality){
formatedAddress = sublocality
}
else if !isNullObject(anyObject: locality){
formatedAddress = locality
}
if !isNullObject(anyObject: formatedAddress){
if shouldAddComma{
formatedAddress = "\(formatedAddress),\(administrative_area_level_1)"
}
}
}
return formatedAddress
}
}
I am building a chat client for iOS using ejabberd and Swift. When I try to retrieve the roster for a user it returns an empty set despite this user having many buddies. Where exactly am I going wrong? I have found similar questions but they seem dated.
Here is my code:
class XMPPController: NSObject {
var hostName: String
var hostPort: UInt16
var password: String
var userJID: XMPPJID
var xmppStream: XMPPStream
var xmppRoster: XMPPRoster!
var xmppRosterStorage: XMPPRosterCoreDataStorage!
init(inputHostName: String, inputUserJIDString: String, inputHostPort: UInt16, inputPassword: String) throws {
guard let formattedUserJID = XMPPJID(string: inputUserJIDString) else {
throw XMPPControllerError.wrongUserJID
}
self.hostName = inputHostName
self.hostPort = inputHostPort
self.password = inputPassword
self.userJID = formattedUserJID
self.xmppStream = XMPPStream()
self.xmppStream.hostName = hostName
self.xmppStream.hostPort = hostPort
self.xmppStream.myJID = userJID
self.xmppRosterStorage = XMPPRosterCoreDataStorage()
self.xmppRoster = XMPPRoster(rosterStorage: xmppRosterStorage)
super.init()
xmppStream.addDelegate(self, delegateQueue: DispatchQueue.main)
xmppRoster.addDelegate(self, delegateQueue: DispatchQueue.main)
xmppRoster.autoFetchRoster = true;
xmppRoster.autoAcceptKnownPresenceSubscriptionRequests = true;
xmppRoster.activate(xmppStream)
print(xmppRosterStorage.jids(for: xmppStream))
}
func connect() {
if self.xmppStream.isDisconnected {
}
do {
try self.xmppStream.connect(withTimeout: 5)
} catch {
print("Error Connecting")
}
}
func disconnect(){
self.xmppStream.disconnect()
}
}
extension XMPPController: XMPPStreamDelegate {
func xmppStreamDidConnect(_ sender: XMPPStream) {
print("Stream: Connected")
try! sender.authenticate(withPassword: password)
}
func xmppStreamDidDisconnect(_ sender: XMPPStream, withError error: Error?) {
print("Stream: Disconnected")
}
func xmppStreamDidAuthenticate(_ sender: XMPPStream) {
let presence = XMPPPresence()
self.xmppStream.send(presence)
print("Stream: Authenticated")
NotificationCenter.default.post(name: Notification.Name(rawValue: authenticatedNotificationKey), object: self)
}
func xmppStream(_ sender: XMPPStream, didNotAuthenticate error: DDXMLElement) {
print("Wrong credentials")
}
}
Thank you.
Answering my own questions.
Two problems.
I did not define the superclass XMPPRosterDelegate for XMPPController.
I did not call
func xmppRosterDidEndPopulating(_ sender: XMPPRoster) {
print(xmppRosterStorage.jids(for: xmppStream))
}
something that could only be done having declared XMPPRosterDelegate.
I cannot see to your add any user your roster. You can try this :
func sendSubscribePresenceFromUserRequest ( _ username : String) {
let otherUser = XMPPJID(string: "\(username)#\(xmppDomain)")!
self.roster.addUser(otherUser, withNickname: "Other user name" , groups: nil, subscribeToPresence: true)
}
Then You call setup XMPPRoster method after 'xmppStreamDidAuthenticate' method just like this :
func rosterSetup() {
let storage = XMPPRosterCoreDataStorage.sharedInstance()
roster = XMPPRoster(rosterStorage: storage!, dispatchQueue: DispatchQueue.main)
if let rosterXmpp = roster {
rosterXmpp.setNickname("Your name" , forUser: stream.myJID!)
rosterXmpp.activate(stream)
rosterXmpp.addDelegate(self, delegateQueue: DispatchQueue.main)
rosterXmpp.autoFetchRoster = true
rosterXmpp.autoClearAllUsersAndResources = true
rosterXmpp.autoAcceptKnownPresenceSubscriptionRequests = true
rosterXmpp.fetch()
}
}
I have a page based app, using RootViewController, ModelViewController, DataViewController, and a SearchViewController.
In my searchViewController, I search for an item and then add or remove that Item to an array which is contained in a Manager class(and UserDefaults), which the modelViewController uses to instantiate an instance of DataViewController with the correct information loaded using the dataObject. Depending on whether an Item was added or removed, I use a Bool to determine which segue was used, addCoin or removeCoin, so that the RootViewController(PageView) will show either the last page in the array, (when a page is added) or the first (when removed).
Everything was working fine until I ran into an error which I can not diagnose, the problem is that when I add a page, the app crashes, giving me a "unexpectadely found nil when unwrapping an optional value"
This appears to be the problem function, in the searchViewController 'self.performSegue(withIdentifier: "addCoin"' seems to be called instantly, even without the dispatchque:
#objc func addButtonAction(sender: UIButton!) {
print("Button tapped")
if Manager.shared.coins.contains(dataObject) {
Duplicate()
} else if Manager.shared.coins.count == 5 {
max()
} else {
Manager.shared.addCoin(coin: dataObject)
CGPrices.shared.getData(arr: true, completion: { (success) in
print(Manager.shared.coins)
DispatchQueue.main.async {
self.performSegue(withIdentifier: "addCoin", sender: self)
}
})
}
searchBar.text = ""
}
Meaning that In my DataViewController, this function will find nil:
func getIndex() {
let index = CGPrices.shared.coinData.index(where: { $0.id == dataObject })!
dataIndex = index
}
I can't find out why it does not wait for completion.
I also get this error about threads:
[Assert] Cannot be called with asCopy = NO on non-main thread.
which is why I try to do the push segue using dispatch que
Here is my searchViewController full code:
import UIKit
class SearchViewController: UIViewController, UISearchBarDelegate {
let selectionLabel = UILabel()
let searchBar = UISearchBar()
let addButton = UIButton()
let removeButton = UIButton()
var filteredObject: [String] = []
var dataObject = ""
var isSearching = false
//Add Button Action.
#objc func addButtonAction(sender: UIButton!) {
print("Button tapped")
if Manager.shared.coins.contains(dataObject) {
Duplicate()
} else if Manager.shared.coins.count == 5 {
max()
} else {
Manager.shared.addCoin(coin: dataObject)
CGPrices.shared.getData(arr: true, completion: { (success) in
print(Manager.shared.coins)
DispatchQueue.main.async {
self.performSegue(withIdentifier: "addCoin", sender: self)
}
})
}
searchBar.text = ""
}
//Remove button action.
#objc func removeButtonActon(sender: UIButton!) {
print("Button tapped")
if Manager.shared.coins.contains(dataObject) {
Duplicate()
} else if Manager.shared.coins.count == 5 {
max()
} else {
Manager.shared.removeCoin(coin: dataObject)
self.performSegue(withIdentifier: "addCoin", sender: self)
}
searchBar.text = ""
}
//Prepare for segue, pass removeCoinSegue Bool depending on remove or addCoin.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "addCoin" {
if let destinationVC = segue.destination as? RootViewController {
destinationVC.addCoinSegue = true
}
} else if segue.identifier == "addCoin" {
if let destinationVC = segue.destination as? RootViewController {
destinationVC.addCoinSegue = false
}
}
}
//Remove button action.
#objc func removeButtonAction(sender: UIButton!) {
if Manager.shared.coins.count == 1 {
removeAlert()
} else {
Manager.shared.removeCoin(coin: dataObject)
print(Manager.shared.coins)
print(dataObject)
searchBar.text = ""
self.removeButton.isHidden = true
DispatchQueue.main.async {
self.performSegue(withIdentifier: "removeCoin", sender: self)
}
}
}
//Search/Filter the struct from CGNames, display both the Symbol and the Name but use the ID as dataObject.
func filterStructForSearchText(searchText: String, scope: String = "All") {
if !searchText.isEmpty {
isSearching = true
filteredObject = CGNames.shared.coinNameData.filter {
// if you need to search key and value and include partial matches
// $0.key.contains(searchText) || $0.value.contains(searchText)
// if you need to search caseInsensitively key and value and include partial matches
$0.name.range(of: searchText, options: .caseInsensitive) != nil || $0.symbol.range(of: searchText, options: .caseInsensitive) != nil
}
.map{ $0.id }
} else {
isSearching = false
print("NoText")
}
}
//Running filter function when text changes.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filterStructForSearchText(searchText: searchText)
if isSearching == true && filteredObject.count > 0 {
addButton.isHidden = false
dataObject = filteredObject[0]
selectionLabel.text = dataObject
if Manager.shared.coins.contains(dataObject) {
removeButton.isHidden = false
addButton.isHidden = true
} else {
removeButton.isHidden = true
addButton.isHidden = false
}
} else {
addButton.isHidden = true
removeButton.isHidden = true
selectionLabel.text = "e.g. btc/bitcoin"
}
}
override func viewDidLoad() {
super.viewDidLoad()
//Setup the UI.
self.view.backgroundColor = .gray
setupView()
}
override func viewDidLayoutSubviews() {
}
//Hide keyboard
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
//Alerts
func removeAlert() {
let alertController = UIAlertController(title: "Can't Remove", message: "\(dataObject) can't be deleted, add another to delete \(dataObject)", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
func Duplicate() {
let alertController = UIAlertController(title: "Duplicate", message: "\(dataObject) is already in your pages!", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
func max() {
let alertController = UIAlertController(title: "Maximum Reached", message: "\(dataObject) can't be added, you have reached the maximum of 5 coins. Please delete a coin to add another.", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
}
and here is the DataViewController
import UIKit
class DataViewController: UIViewController {
#IBOutlet weak var dataLabel: UILabel!
//Variables and Objects.
//The dataObject carries the chosen cryptocurrencies ID from the CoinGecko API to use to get the correct data to load on each object.
var dataObject = String()
//The DefaultCurrency (gbp, eur...) chosen by the user.
var defaultCurrency = ""
//The Currency Unit taken from the exchange section of the API.
var currencyUnit = CGExchange.shared.exchangeData[0].rates.gbp.unit
var secondaryUnit = CGExchange.shared.exchangeData[0].rates.eur.unit
var tertiaryUnit = CGExchange.shared.exchangeData[0].rates.usd.unit
//Index of the dataObject
var dataIndex = Int()
//Objects
let cryptoLabel = UILabel()
let cryptoIconImage = UIImageView()
let secondaryPriceLabel = UILabel()
let mainPriceLabel = UILabel()
let tertiaryPriceLabel = UILabel()
//Custom Fonts.
let customFont = UIFont(name: "AvenirNext-Heavy", size: UIFont.labelFontSize)
let secondFont = UIFont(name: "AvenirNext-BoldItalic" , size: UIFont.labelFontSize)
//Setup Functions
//Get the index of the dataObject
func getIndex() {
let index = CGPrices.shared.coinData.index(where: { $0.id == dataObject })!
dataIndex = index
}
//Label
func setupLabels() {
//cryptoLabel from dataObject as name.
cryptoLabel.text = CGPrices.shared.coinData[dataIndex].name
//Prices from btc Exchange rate.
let btcPrice = CGPrices.shared.coinData[dataIndex].current_price!
let dcExchangeRate = CGExchange.shared.exchangeData[0].rates.gbp.value
let secondaryExchangeRate = CGExchange.shared.exchangeData[0].rates.eur.value
let tertiaryExchangeRate = CGExchange.shared.exchangeData[0].rates.usd.value
let realPrice = (btcPrice * dcExchangeRate)
let secondaryPrice = (btcPrice * secondaryExchangeRate)
let tertiaryPrice = (btcPrice * tertiaryExchangeRate)
secondaryPriceLabel.text = "\(secondaryUnit)\(String((round(1000 * secondaryPrice) / 1000)))"
mainPriceLabel.text = "\(currencyUnit)\(String((round(1000 * realPrice) /1000)))"
tertiaryPriceLabel.text = "\(tertiaryUnit)\(String((round(1000 * tertiaryPrice) / 1000)))"
}
//Image
func getIcon() {
let chosenImage = CGPrices.shared.coinData[dataIndex].image
let remoteImageUrl = URL(string: chosenImage)
guard let url = remoteImageUrl else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
DispatchQueue.main.async {
self.cryptoIconImage.image = UIImage(data: data)
}
}
}.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
// for family in UIFont.familyNames.sorted() {
// let names = UIFont.fontNames(forFamilyName: family)
// print("Family: \(family) Font names: \(names)")
// }
// Do any additional setup after loading the view, typically from a nib.
self.setupLayout()
self.getIndex()
self.setupLabels()
self.getIcon()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.dataLabel!.text = dataObject
view.backgroundColor = .lightGray
}
}
Edit: CGPrices Class with getData method:
import Foundation
class CGPrices {
struct Coins: Decodable {
let id: String
let name: String
let symbol: String
let image: String
let current_price: Double?
let low_24h: Double?
//let price_change_24h: Double?
}
var coinData = [Coins]()
var defaultCurrency = ""
var coins = Manager.shared.coins
var coinsEncoded = ""
static let shared = CGPrices()
func encode() {
for i in 0..<coins.count {
coinsEncoded += coins[i]
if (i + 1) < coins.count { coinsEncoded += "%2C" }
}
print("encoded")
}
func getData(arr: Bool, completion: #escaping (Bool) -> ()) {
encode()
let urlJSON = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=\(coinsEncoded)"
guard let url = URL(string: urlJSON) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let coinsData = try JSONDecoder().decode([Coins].self, from: data)
self.coinData = coinsData
completion(arr)
} catch let jsonErr {
print("error serializing json: \(jsonErr)")
print(data)
}
}.resume()
}
func refresh(completion: () -> ()) {
defaultCurrency = UserDefaults.standard.string(forKey: "DefaultCurrency")!
completion()
}
}
I figured it out.
The problem was inside my getData method I was not updated the coins array:
var coinData = [Coins]()
var defaultCurrency = ""
var coins = Manager.shared.coins
var coinsEncoded = ""
static let shared = CGPrices()
func encode() {
for i in 0..<coins.count {
coinsEncoded += coins[i]
if (i+1)<coins.count { coinsEncoded+="%2C" }
}
print("encoded")
}
I needed to add this line in getData:
func getData(arr: Bool, completion: #escaping (Bool) -> ()) {
//Adding this line to update the array so that the URL is appended correctly.
coins = Manager.shared.coins
encode()
let urlJSON = "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=\(coinsEncoded)"
This would fix the finding nil in the DataViewController, but the app would still crash do to updating UI Elements on a background thread, as the segue was called inside the completion handler of the getData method. to fix this, I used DispatchQue.Main.Async on the segue inside the getData method in the addButton function, to ensure that everything is updated on the main thread, like so:
#objc func addButtonAction(sender: UIButton!) {
print("Button tapped")
if Manager.shared.coins.contains(dataObject) {
Duplicate()
} else if Manager.shared.coins.count == 5 {
max()
} else {
Manager.shared.addCoin(coin: dataObject)
print("starting")
CGPrices.shared.getData(arr: true) { (arr) in
print("complete")
print(CGPrices.shared.coinData)
//Here making sure it is updated on main thread.
DispatchQueue.main.async {
self.performSegue(withIdentifier: "addCoin", sender: self)
}
}
}
searchBar.text = ""
}
Thanks for all the comments as they helped me to figure this out, and I learned a lot in doing so. Hopefully this can help someone else in their thought process when debugging, as one can get so caught up in one area of a problem, and forget to take a step back and look to other areas.
I have an embedded CPU sending strings through BLE UART to a iPhone. I can connect to the device, setup the UART characteristics, but it only reads two characters and didUpdateValueFor is called no more. I have to be setting something up wrong. There there seems to be some buffer overflow or it's not rest for each read.
I want to be able to setup a UART connection between the board and the iPhone. I can take care of parsing.
Right not I'm sending the character '0' every 250 ms from the board. It picks up two '0's and the event is not called anymore.
// ViewController.swift
import UIKit
import CoreBluetooth
class ScannerVC: UIViewController, CBCentralManagerDelegate {
var centralManager: CBCentralManager!
var peripheral: CBPeripheral!
var scannedGrowZeboDevices: [CBPeripheral] = []
var timer: Timer!
var connectionVC: ConnectionVC!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
var msg = ""
switch (central.state) {
case .poweredOff:
msg = "Bluetooth is powered off"
case .poweredOn:
msg = "Bluetooth is powered on"
timer = Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(ScannerVC.stopScanning), userInfo: nil, repeats: false)
centralManager.scanForPeripherals(withServices: nil, options: nil)
case .unsupported:
msg = "Bluetooth is unsopported"
case .resetting:
msg = "The BLE Manager is resetting; a state update is pending"
case .unknown:
msg = "BThe state of the BLE Manager is unkown"
default:
break
}
print("Status: \(msg)")
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if let peripheralName = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
if peripheralName.contains("Grow") || peripheralName.contains("paul") {
print("Peripheral Name: \(peripheralName)")
for existing in scannedGrowZeboDevices {
if existing.identifier == peripheral.identifier {
print("Peripheral already exists, returning")
return
}
}
// adding peripheral to the array //
scannedGrowZeboDevices.append(peripheral)
self.peripheral = peripheral
print("There are \(scannedGrowZeboDevices.count) peripherals in the array")
}
}
}
#objc func stopScanning() {
timer.invalidate()
centralManager.stopScan()
print("Scanning stopped")
performSegue(withIdentifier: "toNavigation", sender: scannedGrowZeboDevices)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toNavigation" {
if let navController = segue.destination as? UINavigationController {
let connectionVC = navController.viewControllers.first as! ConnectionVC
centralManager.delegate = connectionVC
connectionVC.centralManager = self.centralManager
connectionVC.growzeboDevices = sender as! [CBPeripheral?]
}
}
}
}
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
//*********************************************************************************
// Added May 28, 2018
// This shows there is a connection to the peripheral
//*********************************************************************************
print("[DEBUG] Connected to peripheral \(peripheral.identifier.uuidString)")
// self.activePeripheral = peripheral
peripheral.discoverServices(nil)
}
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
let defaults = UserDefaults.standard
if let services = peripheral.services as [CBService]? {
//print("Discovered Services for: \(String(describing: peripheral.name))")
for service in services {
peripheral.discoverCharacteristics(nil, for: service)
print("Found serial port service")
}
//**********************************************************************
// Added May 28, 2018
// Add the TX and RX for the discovered services
//*********************************************************************
print("[DEBUG] Found services for peripheral: \(peripheral.identifier.uuidString)")
// for service in peripheral.services! {
// let theCharacteristics = [CBUUID(serialPortServiceUuid), CBUUID(serialPortServiceUuid)]
// }
if let name = peripheral.name {
print("Connected to: \(name)")
if let _ = defaults.dictionary(forKey: peripheral.name!) {
// To do if the card has already been connected before
} else {
// To do if the card getting connected first time
settingsToSave["name"] = peripheral.name!
settingsToSave["connected"] = true
defaults.set(settingsToSave, forKey: peripheral.name!)
let detailsVC = storyboard?.instantiateViewController(withIdentifier: "detailsVC") as! DetailsVC
centralManager.delegate = detailsVC
let cm = centralManager
detailsVC.centralManager = cm
detailsVC.peripheral = peripheral
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.navigationController?.pushViewController(detailsVC, animated: true)
}
}
}
growzebosTableView.reloadData()
}
}
//************************** Functionality Starts here *******************************//
override func viewDidLoad() {
super.viewDidLoad()
vegSettings = GrowzeboBoxModel()
bloomSettings = GrowzeboBoxModel()
customSettings = GrowzeboBoxModel()
setupPickerViews()
//*************************************************
// Added May 15, 2018
// Thiz sets up the object for picking AUX values
setupPumpPickerViews()
let defaults = UserDefaults.standard
savedSettings = defaults.dictionary(forKey: peripheral.name!)!
deviceName.text = peripheral.name
// state.text = String("\(peripheral.state)")
let buf = [UInt8]()
// let buf: [UInt8] = [10000]
dataRxCredits = Data.init(buf)
let buf2: [UInt8] = [0xFF]
disconnectCredit = Data.init(bytes: buf2)
// adding targets to sliders //
func open() -> Bool {
var ok = false
if peripheral.state == .connected {
state.text = "Connected"
// nTxCredits = 0
// pendingData = nil
pendingCredits = false
peripheral.delegate = self
initServicesAndCharacteristics()
openSerialPortService()
ok = true
}
else{
state.text = "Disconnected"
}
return ok
}
// Initialize all the required services and characteristics //
func initServicesAndCharacteristics() {
var s: CBService?
var c: CBCharacteristic?
service = nil
creditsCharacteristic = nil
fifoCharacteristic = nil
deviceIdService = nil
fwVersionCharacteristic = nil
modelNumberCharacteristic = nil
for i in 0..<peripheral.services!.count {
s = peripheral.services![i]
if let data = s?.uuid.data {
let dataInBytes = [UInt8](data)
if (s?.uuid.data.count == 16) && (memcmp(dataInBytes, serialPortServiceUuid, 16) == 0) {
service = s
}
else if (s?.uuid.data.count == 2) && (memcmp(dataInBytes, deviceIdServiceUuid, 2) == 0) {
deviceIdService = s
}
}
}
if service != nil {
if service!.characteristics != nil {
for i in 0..<service!.characteristics!.count {
c = service!.characteristics![i]
if let data = c?.uuid.data {
let dataInBytes = [UInt8](data)
if (c?.uuid.data.count == 16) && (memcmp(dataInBytes, serialPortFifoCharactUuid, 16) == 0) {
fifoCharacteristic = c
}
else if (c?.uuid.data.count == 16) && (memcmp(dataInBytes, creditsCharactUuid, 16) == 0) {
creditsCharacteristic = c
}
}
}
}
}
if deviceIdService != nil {
if deviceIdService!.characteristics != nil {
for i in 0..<deviceIdService!.characteristics!.count {
c = deviceIdService!.characteristics![i]
if (c?.uuid.data.count == 2) {
modelNumberCharacteristic = c
}
else if (c?.uuid.data.count == 2) {
fwVersionCharacteristic = c
}
}
}
}
}
// Opening serial port //
func openSerialPortService() {
// serialPortVersion = 2
peripheral.setNotifyValue(true, for: creditsCharacteristic!)
peripheral.setNotifyValue(true, for: fifoCharacteristic!)
peripheral.writeValue(dataRxCredits!, for: creditsCharacteristic!, type: .withoutResponse)
}
// Closing serial port //
func closeSerialPortService() {
peripheral.setNotifyValue(false, for: creditsCharacteristic!)
peripheral.setNotifyValue(false, for: fifoCharacteristic!)
peripheral.writeValue(disconnectCredit!, for: creditsCharacteristic!, type: .withoutResponse)
}
// Send a serial message to the card //
#objc func sendSerialMessage(_ message: String) {
if (peripheral.state != .connected)
{
SVProgressHUD.dismiss()
navigationController?.popToRootViewController(animated: true)
print("Disconected")
}
let msgToSend = message.appendingFormat("\r")
//let msg = "show\r"
let buf: [UInt8] = Array(msgToSend.utf8)
let data = Data(buf)
peripheral.writeValue(data, for: fifoCharacteristic!, type: CBCharacteristicWriteType.withoutResponse)
print("\(message) sent")
commandname = message
}