I'm very new in programing, I have been learning swift5 for a few months now. I'm trying to make an APP that scan a 1D or 2D barcode, take the string and send it to an API endpoint which will give me back some info, for example, product number and batch number.
For the barcode part I'm using a really nice software from GitHub called BarcodeScanner. It is running without any issue and I'm able to get the string from the barcode it reads.
Once I have the string I'm able to send it to the API endpoint but then it gets stuck there.
If I run the same code in a playground it works without problems, it makes the request and I can see the answer in JSON from the server. If I run the code on the iPhone then it gets stuck. Of course I cannot use the scanning part when in playground, I use it to test the networking part and communication with the API.
Here is my Networking.swift file, it has some print() used for debug:
import Foundation
import UIKit
protocol NetworkManagerDelegate: class {
func didUpdatePharmo(pharmo: PharmoModel)
func didUpdateBarcode(barcode: BarcodeModel)
}
public struct NetworkManager {
var delegate: NetworkManagerDelegate?
let pharmoURL = "https://someurl.com/api"
let pharmoEndpoint = "https://someendpoint.com/api"
func fetchData(productNumber: String) {
let urlString = "\(pharmoURL)&number=\(productNumber)"
performRequestProductInfo(urlString: urlString)
print(urlString)
}
func fetchEndpoint(dataMatrix: String) {
let urlString = "\(pharmoEndpoint)&datamatrix=\(dataMatrix)"
performRequestEndpoint(urlString: urlString)
print(urlString)
}
func performRequestEndpoint(urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = session.dataTask(with: request) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let response = response as? HTTPURLResponse {
print("EndPoint HTTP Status code: \(response.statusCode)")
}
if let safeData = data {
if let barcode = self.parseJSONEndpoint(endPointData: safeData) {
self.delegate?.didUpdateBarcode(barcode: barcode)
}
}
}
task.resume()
}
}
func parseJSONEndpoint(endPointData: Data) -> BarcodeModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(BarcodeData.self, from: endPointData)
let productNumber = decodedData.product_number
let batchNumber = decodedData.batchnumber
let barcode = BarcodeModel(productNumber: productNumber, batchNumber: batchNumber)
print(batchNumber)
return barcode
} catch {
print(error)
return nil
}
}
func performRequestProductInfo(urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = session.dataTask(with: request) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let response = response as? HTTPURLResponse {
print("ProductInfo HTTP Status code: \(response.statusCode)")
}
if let safeData = data {
if let pharmo = self.parseJSONProductInfo(pharmoData: safeData) {
self.delegate?.didUpdatePharmo(pharmo: pharmo)
}
}
}
task.resume()
}
}
func parseJSONProductInfo(pharmoData: Data) -> PharmoModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(PharmoData.self, from: pharmoData)
let id = decodedData.id
let number = decodedData.number
let name = decodedData.name
let atcCode = decodedData.atc_code
let imagePath = decodedData.image_path
let displayImage = decodedData.display_image
let storageGroupId = decodedData.storage_group_id
let animalGroupId = decodedData.animal_group_id
let udlevbest = decodedData.udlevbest
let packageSizeText = decodedData.package_size_text
let packageSizeNum = decodedData.package_size_num
let unitCode = decodedData.unit_code
let strengthText = decodedData.strength_text
let prodPrice = decodedData.prod_price
let aupPrice = decodedData.aup_price
let aupInstPrice = decodedData.aup_inst_price
let animalGroups = decodedData.animal_groups
let udleveringsbestemmelse = decodedData.udleveringsbestemmelse
let animalGroup = decodedData.animal_group
let storageGroup = decodedData.storage_group
let species = decodedData.species
let indications = decodedData.indications
let substances = decodedData.substances
let manufacturer = decodedData.manufacturer
let pharmo = PharmoModel(id: id, number: number, name: name, atc_code: atcCode, image_path: imagePath, display_image: displayImage, storage_group_id: storageGroupId, animal_group_id: animalGroupId, udlevbest: udlevbest, package_size_text: packageSizeText, package_size_num: packageSizeNum, unit_code: unitCode, strength_text: strengthText, prod_price: prodPrice, aup_price: aupPrice, aup_inst_price: aupInstPrice, animal_groups: animalGroups, udleveringsbestemmelse: udleveringsbestemmelse, animal_group: animalGroup, storage_group: storageGroup, species: species, indications: indications, substances: substances, manufacturer: manufacturer)
print(name)
return pharmo
} catch {
print(error)
return nil
}
}
}
From the ViewController.swift running the barcode scanner I call the function fetchEndpoint(dataMatrix: String)
extension ViewController: BarcodeScannerCodeDelegate {
func scanner(_ controller: BarcodeScannerViewController, didCaptureCode code: String, type: String) {
print("Barcode Data: \(code)")
print("Symbology Type: \(type)")
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
networkManager.fetchEndpoint(dataMatrix: code)
}
}
}
From the console output I can see this 2 print() statements from ViewController.swift:
print("Barcode Data: \(code)")
print("Symbology Type: \(type)")
I can also see this print() statement from Networking.swift:
print(urlString)
But nothing happends after that. Again if I run the same code in a playground it works fine. I have also made requests using Postman, so I can see the JSON file and be sure the urlstring is working.
Anyone have some idea what am I doing wrong?
I was able to find the problem. The string being read from the scanner had an non ASCII character.
At any point XCode said anything about a non ASCII character, I had to copy the string into a playground, then I received this error:
Unprintable ASCII character found in source file
I copied the string into a terminal shell and I was able to see the character.
datamatrix=^]01036611030467831722113010RXT0711GC
I will try to use URLComponents or percent encoding
Related
I was trying to learn the basics of networking with openweather api.
Implemented a very basic struct like this.
protocol WeatherManagerDelegate {
func didUpdateWeather(_ weatherManager : WeatherManager, weather : WeatherModel)
func didFailWithError(error: Error)
}
struct WeatherManager {
var delegate : WeatherManagerDelegate?
var temp : Double = 0.0
let weatherURL = "https://api.openweathermap.org/data/2.5/weather?appid=40ca58efce193db0fc801564afb08283&units=metric"
func fetchWheather(cityName : String){
let urlString = "\(weatherURL)&q=\(cityName)"
performRequest(with: urlString)
}
func performRequest(with urlString: String){
if let url = URL(string: urlString){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url){ (data, response, error) in
if error != nil{
delegate?.didFailWithError(error: error!)
return
}
if let safedata = data {
if let weather = self.parseJSON(weatherData: safedata){
// let WeatherVC = WeatherViewController()
self.delegate!.didUpdateWeather(self, weather: weather)
}
print("Data is \(safedata)")
}
}
task.resume()
}
}
func parseJSON(weatherData:Data)-> WeatherModel?{
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(WeatherData.self, from: weatherData)
let id = decodedData.weather[0].id
let temp = decodedData.main.temp
let name = decodedData.name
let weather = WeatherModel(conditionID: id, cityName: name, temperature: temp)
return weather
} catch{
print("error is \(error)")
delegate?.didFailWithError(error: error)
return nil
}
}
}
But the issue is none of the print statements inside the requests are giving any outputs , nor i am able to update the UI. But the URL is giving the correct JSON Response when tried in browser
I am using the latest XCode on iOS 15 (iPhone 11 Device) with M1 Pro chip mac.
Found some threads which mentions to use "open with rosetta" but none of which worked.
also, not getting any errors on the console
Any solution?
Edit : Called in VC like this:
func textFieldDidEndEditing(_ textField: UITextField) {
if let city = searchTextField.text {
weatherManager.fetchWeather(cityName: city)
}
searchTextField.text = ""
}
Please try using:
let session = URLSession.shared
…instead of creating local session variable within the function scope
And if error != nil then don't return from that block. Simply use if-else for error handling.
Hello i am trying to get result based on a result from another api.The getLatest function is going to get the latest object and will return the number then i want to generate a list of urls with the latest number and then sequentially get the data in the function getXKCDData.How can i achieve this.The dispatchgroup i have on there works sometimes but doesnt work all the time because generateUrlList is called before getting the latest number and it generate the wrong url.Which causes The operation couldn’t be completed. (XKCD_Comics.NetworkError error 1.) error.How can i achieve this.
final class NetworkManager {
public static var shared = NetworkManager()
private let sessions = URLSession.shared
private func generateUrlList(latestXKCD: Int) -> [String] {
print("Here at generateUrlList")
var urls = [String]()
for i in latestXKCD-100...latestXKCD{
let xkcd_url = "https://xkcd.com/\(i)/info.0.json"
urls.append(xkcd_url)
}
return urls
}
private func getLatest(completion: #escaping(Int) -> ()){
print("Here at get latest")
guard let url = URL(string: "https://xkcd.com/info.0.json") else { return }
let task = sessions.dataTask(with: url) { data, response, error in
guard let jsonData = data else { return }
do {
let decoder = JSONDecoder()
let response = try decoder.decode(Comics.self, from: jsonData)
completion(response.num)
}catch {
completion(0)
}
}
task.resume()
}
public func getXKCDData(completion: #escaping(Result<Comics,NetworkError>) -> Void){
let group = DispatchGroup()
var latestID = 0
group.enter()
self.getLatest { (result) in
latestID = result
group.leave()
}
let urlList = generateUrlList(latestXKCD: latestID)
print(urlList)
print("Here at getXKCDData")
for url in urlList {
group.enter()
guard let url = URL(string: url) else {
completion(.failure(.InvalidURL))
return
}
let task = sessions.dataTask(with: url) { data, response, error in
guard let jsonData = data else {
completion(.failure(.NoDataAvailable))
return
}
do {
let decoder = JSONDecoder()
let response = try decoder.decode(Comics.self, from: jsonData)
completion(.success(response))
group.leave()
}catch {
completion(.failure(.CanNotProcessData))
}
}
task.resume()
}
}
}
Two days already of learning how to make my own network layer to fetching data from server using API and json decoder
But after two days of many lessons online I can't find what's after creating the network layer in Swift 5,
I'm getting json data in output by print(data) in serviceLayer Class file but can't print the data in SwiftUI List View?
here's the serviceLayer.swift file
import Foundation
class ServiceLayer {
// 1.
class func request<T: Decodable>(router: Router, completion: #escaping (Result<[String: [T]], Error>) -> ()) {
// 2.
var components = URLComponents()
components.scheme = router.scheme
components.host = router.host
components.path = router.path
components.queryItems = router.parameters
// 3.
guard let url = components.url else { return }
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = router.method
// 4.
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: urlRequest) { data, response, error in
// 5.
guard error == nil else {
completion(.failure(error!))
print(error?.localizedDescription)
return
}
guard response != nil else {
return
}
guard let data = data else {
return
}
// 6.
let responseObject = try! JSONDecoder().decode([String: [T]].self, from: data)
// 7.
DispatchQueue.main.async {
// 8.
completion(.success(responseObject))
}
}
dataTask.resume()
}
}
And here is the model I'm using
struct CollectionItem: Decodable {
let title: String
let id: Int
}
In the swift ui define a property with #State property wrapper, assign the responseObject to this property. SwiftUI automatically reloads when the #State property is set.
If you are willing to use Combine, https://dev.to/kevinmaarek/getting-started-with-swiftui-and-combine-dd8
I am using amazon product advertising api for search product. Installed awscore and alamofire cocopods. Done functionality for getting signature and added parameters for item search to get product images, title and description in table view list.
Here is the code i tried for getting amazon search:
private func signedParametersForParameters(parameters: [String: String]) -> [String: String] {
let sortedKeys = Array(parameters.keys).sorted(by: <)
let query = sortedKeys.map { String(format: "%#=%#", $0, parameters[$0] ?? "") }.joined(separator: "&")
let stringToSign = "GET\nwebservices.amazon.in\n/onca/xml\n\(query)"
print("stringToSign::::\(stringToSign)")
let dataToSign = stringToSign.data(using: String.Encoding.utf8)
let signature = AWSSignatureSignerUtility.hmacSign(dataToSign, withKey: CameraViewController.kAmazonAccessSecretKey, usingAlgorithm: UInt32(kCCHmacAlgSHA256))!
var signedParams = parameters;
signedParams["Signature"] = urlEncode(signature)
print("urlencodesignature::\(urlEncode(signature))")
return signedParams
}
public func urlEncode(_ input: String) -> String {
let allowedCharacterSet = (CharacterSet(charactersIn: "!*'();:#&=+$,/?%#[] ").inverted)
if let escapedString = input.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) {
return escapedString
}
return ""
}
func send(url: String) -> String {
// activityIndicator.startAnimating()
guard let url = URL(string: url) else {
print("Error! Invalid URL!") //Do something else
// activityIndicator.stopAnimating()
return ""
}
print("send URL: \(url)")
let request = URLRequest(url: url)
let semaphore = DispatchSemaphore(value: 0)
var data: Data? = nil
URLSession.shared.dataTask(with: request) { (responseData, _, _) -> Void in
data = responseData
print("send URL session data: \(String(describing: data))")
let parser = XMLParser(data: data!)
parser.delegate = self as? XMLParserDelegate
if parser.parse() {
print(self.results ?? "No results")
}
semaphore.signal()
}.resume()
// activityIndicator.stopAnimating()
semaphore.wait(timeout: .distantFuture)
let reply = data.flatMap { String(data: $0, encoding: .utf8) } ?? ""
return reply
}
public func getSearchItem(searchKeyword: String) -> [String:AnyObject]{
let timestampFormatter: DateFormatter
timestampFormatter = DateFormatter()
timestampFormatter.timeZone = TimeZone(identifier: "GMT")
timestampFormatter.dateFormat = "YYYY-MM-dd'T'HH:mm:ss'Z'"
timestampFormatter.locale = Locale(identifier: "en_US_POSIX")
// let responsegroupitem: String = "ItemAttributes"
// let responsegroupImages:String = "Images"
// activityIndicator.startAnimating()
let operationParams: [String: String] = [
"Service": "AWSECommerceService",
"Operation": "ItemSearch",
"ResponseGroup": "Images,ItemAttributes",
"IdType": "ASIN",
"SearchIndex":"All",
"Keywords": searchKeyword,
"AWSAccessKeyId": urlEncode(CameraViewController.kAmazonAccessID),
"AssociateTag": urlEncode(CameraViewController.kAmazonAssociateTag),
"Timestamp": urlEncode(timestampFormatter.string(from: Date()))]
let signedParams = signedParametersForParameters(parameters: operationParams)
let query = signedParams.map { "\($0)=\($1)" }.joined(separator: "&")
let url = "http://webservices.amazon.in/onca/xml?" + query
print("querydata::::\(query)")
let reply = send(url: url)
print("reply::::\(reply)")
// activityIndicator.stopAnimating()
return [:]
}
Created bridging header file #import .
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
getSearchItem(searchKeyword: searchKeyword)
}
Here is my console output:
My issue is when tapping search button product searched was not listing. What mistake done i don't know. Can anyone help me out of this pls..
According to the documentation:
The HTTPRequestURI component is the HTTP absolute path component of the URI up to, but not including, the query string. If the HTTPRequestURI is empty, use a forward slash ( / ).
HTTPRequestURI is always "/onca/xml" for Product Advertising API. HTTPVerb is either GET or POST.
Try just setting requestURL to "/onca/xml" instead of the full URL you shouldn't be sending the full URL or the query string in this part.
Also you need to percent encode the values that you are sending. You are sending commas in the response group property which should be percent encoded
let operationParams: [String: String] = [
"Service": "AWSECommerceService",
"Operation": "ItemSearch",
"ResponseGroup": urlEncode("Images,ItemAttributes"),
"IdType": "ASIN",
"SearchIndex":"All",
"Keywords": urlEncode(searchKeyword),
"AWSAccessKeyId": urlEncode(CameraViewController.kAmazonAccessID),
"AssociateTag": urlEncode(CameraViewController.kAmazonAssociateTag),
"Timestamp": urlEncode(timestampFormatter.string(from: Date()))]
let stringToSign = "GET\n/onca/xml\n\(query)"
Note: You should be using https instead of http
I've been following an guide in the hope of learning how to use MYSQL with IOS apps.
However the guide is a little bit outdated, and I'm using swift 3 and I been editing the code to fix a few bugs.
I have come down to a final problem, which is after I changed from using URL to NSURL, I can't use the "DataTask" anymore..
I have no idea how to replace this code of line.
import Foundation
protocol HomeModelProtocol: class {
func itemsDownloaded(items: NSArray)
}
class HomeModel: NSObject, NSURLSessionDataDelegate {
//properties
weak var delegate: HomeModelProtocol!
let urlPath = "http://iosquiz.com/service.php" //this will be changed to the path where service.php lives
func downloadItems() {
let url: NSURL = NSURL(string: urlPath)!
let defaultSession = Foundation.NSURLSessionConfiguration
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
}
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
do{
jsonResult = try NSJSONSerialization.jsonObject(with: data, options: NSJSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
let locations = NSMutableArray()
for i in 0 ..< jsonResult.count
{
jsonElement = jsonResult[i] as! NSDictionary
let location = LocationModel()
//the following insures none of the JsonElement values are nil through optional binding
if let name = jsonElement["Name"] as? String,
let address = jsonElement["Address"] as? String,
let latitude = jsonElement["Latitude"] as? String,
let longitude = jsonElement["Longitude"] as? String
{
location.name = name
location.address = address
location.latitude = latitude
location.longitude = longitude
}
locations.addObject(location)
}
I have come down to a final problem, which is after I changed from using URL to NSURL, I can't use the "DataTask" anymore..
Why did you switch from URL to NSURL? That's moving in the wrong direction. URL is the Swift bridge for NSURL. It should replace NSURL in all new code.
Switch back to URL. If you must use NSURL, you'll have to add an as URL when you use it in dataTask(with:), since that method expects an URL.
There's a deeper problem here. You're using a configuration as though it were a session. Here's the code I believe you mean:
// vvv Changed NSURLSessionDataDelegate to URLSessionDataDelegate
class HomeModel: NSObject, URLSessionDataDelegate {
weak var delegate: HomeModelProtocol? // <-- Avoid ! for this
let urlPath = "http://iosquiz.com/service.php"
func downloadItems() {
let url = URL(string: urlPath)! // <-- Changed NSURL to URL
let defaultSession = URLSession.shared // <-- Use URLSession, not URLSessionConfiguration
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
}