I'm creating an iphone app using swift 2. I got the app to be able to read barcodes and display the barcode. Also I learned core data and using it to import a .csv file with a bunch of product information which includes the barcode. Now when a barcode is scanned, I want it to search through the file and display the information relating to it. Here are parts of my code, how will I go about doing this? To be honest, I have programmers block right now and had for the past two days. Any help or guidance will be appreciated.
Here is the CSV Parser:
func parseCSV (contentsOfURL: NSURL, encoding: NSStringEncoding) -> [(title:String, price:String, weight:String, box_length:String, box_width:String, box_height:String, sku:String, barcodeNum:String )]? {
// Load the CSV file and parse it
let delimiter = ","
var items:[(title:String, price:String, weight:String, box_length:String, box_width:String, box_height:String, sku:String, barcodeNum:String )]?
do {
let content = try String(contentsOfURL: contentsOfURL, encoding: encoding)
print(content)
items = []
let lines:[String] = content.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) as [String]
for line in lines {
var values:[String] = []
if line != "" {
// For a line with double quotes
// we use NSScanner to perform the parsing
if line.rangeOfString("\"") != nil {
var textToScan:String = line
var value:NSString?
var textScanner:NSScanner = NSScanner(string: textToScan)
while textScanner.string != "" {
if (textScanner.string as NSString).substringToIndex(1) == "\"" {
textScanner.scanLocation += 1
textScanner.scanUpToString("\"", intoString: &value)
textScanner.scanLocation += 1
} else {
textScanner.scanUpToString(delimiter, intoString: &value)
}
// Store the value into the values array
values.append(value as! String)
// Retrieve the unscanned remainder of the string
if textScanner.scanLocation < textScanner.string.characters.count {
textToScan = (textScanner.string as NSString).substringFromIndex(textScanner.scanLocation + 1)
} else {
textToScan = ""
}
textScanner = NSScanner(string: textToScan)
}
// For a line without double quotes, we can simply separate the string
// by using the delimiter (e.g. comma)
} else {
values = line.componentsSeparatedByString(delimiter)
}
// Put the values into the tuple and add it to the items array
//(title:String, price:String, weight:String, box_length:String, box_width:String, box_height:String, sku:String, barcodeNum:String )
let item = (title: values[0], sku: values[1], price: values[2], weight: values[3], box_length: values[4], box_width: values[5], box_height: values[6], barcodeNum: values[7])
items?.append(item)
}
}
} catch {
print(error)
}
return items
}
When Barcode is detected:
func barcodeDetected(code: String) {
print(code)
if(barcodeDelegate != nil){
barcodeDelegate!.barcodeFound(code)
}
// Let the user know we've found something.
let alert = UIAlertController(title: "Found a Barcode!", message: code, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Search", style: UIAlertActionStyle.Destructive, handler: { action in
// Remove the spaces.
let trimmedCode = code.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
// EAN or UPC?
// Check for added "0" at beginning of code.
let trimmedCodeString = "\(trimmedCode)"
var trimmedCodeNoZero: String
if trimmedCodeString.hasPrefix("0") && trimmedCodeString.characters.count > 1 {
trimmedCodeNoZero = String(trimmedCodeString.characters.dropFirst())
// Send the doctored UPC to DataService.searchAPI()
DataService.searchCode(trimmedCodeNoZero)
} else {
// Send the doctored EAN to DataService.searchAPI()
DataService.searchCode(trimmedCodeString)
}
self.navigationController?.popViewControllerAnimated(true)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
Dataservice.swift file
import Foundation
class DataService {
static let dataService = DataService()
static func searchCode(codeNumber: String) {
}
}
I can post more of my project if it helps, but I feel like this is already too much information, anything will help at this point on how to tackle this.
Related
I'm still in the early stages with SwiftUI. Everything's been fine up until now, but I'm running into problems with using GCD - never worked with multi-threading before.
I am getting data from various sensors over TCP continuously running in the background. All this seems fine, I can see the data coming across and it appears to be getting stored where I want it...
Raw data
$AIMTW,26.1,C*1E
Contents of struct
(src: "AI", timeStamp: 2021-09-11 10:43:01 +0000, temp: 26.1, units: "ºC")
I think I'm getting the data processing happening on the main thread where it should update my View, but it doesn't update. I expect I've missed something pretty fundamental, but I just can't work out what it is. Any help would be appreciated.
This should hopefully be enough to reproduce the problem. Originally the code would be reading in a constant stream of data from a TCP source; I have modified it to read from a text file. The View should update after each line of the text file.
import Foundation
struct NMEA{
// Mean Temperature of Water
var mtw = (src: "", // Source
timeStamp: Date(), // Generated Time Stamp
temp: 0.0, // temperature
units: "") // unit of measurement, Celsius (C)
}
import SwiftUI
struct ContentView: View {
#ObservedObject var nmeaHandler = NMEAhandler()
var body: some View {
HStack{
Text("Water Temperature: ")
Text(String(nmeaHandler.nmea.mtw.temp))
Text(nmeaHandler.nmea.mtw.units)
}
.onAppear{
nmeaHandler.fetchNMEA()
}
}
}
import Foundation
class NMEAhandler: ObservableObject{
#Published var nmea = NMEA()
func fetchNMEA() {
DispatchQueue.global(qos: .userInitiated).async { self.receiveNMEA()
}
}
private func receiveNMEA() {
do {
let path = Bundle.main.path(forResource: "reprex-input", ofType: "txt") ?? "" // file path for file "data.txt"
let message = try String(contentsOfFile: path, encoding: String.Encoding.utf8)
print(message)
message.enumerateLines(invoking: { (line, stop) -> () in
print("Line = \(line)")
})
DispatchQueue.main.async {
self.prepareSentences(data: message)
print("The View updates now.")
}
} catch {
print("Error:", error)
}
}
private func prepareSentences(data: String){
var messages: [String] = []
var fields: [String] = []
if data != "-"{
messages = data.components(separatedBy: "\r\n")
for message in messages{
fields = message.components(separatedBy: ",")
let lastField = fields.last
let splitLast = lastField?.components(separatedBy: "*") ?? [""]
fields.removeLast(1)
fields.append(contentsOf: splitLast)
let valid = checksum(message: message, checksum: fields.last ?? "")
if valid{
parseNMEA(fields: fields)
// The View should be updated by now
}
}
return
}
}
private func checksum(message: String, checksum: String) -> Bool {
let messageLength = message.count
let firstCharacter = message.first
if messageLength < 5 || firstCharacter != "$"{
return false
}
let actualMessage = message.slice(1, messageLength - 4)
var calcChecksum = 0
for c in actualMessage.utf8 {
calcChecksum = calcChecksum ^ Int(c)
}
if calcChecksum == Int(checksum, radix: 16) ?? 999{
return true
}
return false
}
private func parseNMEA(fields: [String]){
switch fields[0].slice(3, 5){
case "MTW":
nmea.mtw.src = fields[0].slice(1, 2)
nmea.mtw.timeStamp = Date()
nmea.mtw.temp = Double(fields[1]) ?? 0
nmea.mtw.units = "º" + fields[2]
print("MTW: \(nmea.mtw)")
default:
print("Missing sentence: \(fields[0].slice(1, 2))")
return
}
}
}
import Foundation
extension StringProtocol {
func slice(_ start: Int, _ end: Int) -> String {
if self.count >= end {
let lower = index(self.startIndex, offsetBy: start)
let upper = index(lower, offsetBy: end - start)
return String(self[lower...upper])
} else {
return self as! String
}
}
}
Text file used for input:
$AIMTW,24.7,C*1A
$AIMTW,24.9,C*14
$AIMTW,24.8,C*15
$SDMTW,19.4,C*08
$AIMTW,24.9,C*14
$SDMTW,19.4,C*08
$SDMTW,19.4,C*08
$AIMTW,24.7,C*1A
$SDMTW,19.6,C*0A
$AIMTW,23.9,C*13
I need some code to make sure that if a whole word exists in a return formatted text file it is accepted and that, if only part of it is present, it is not considered.
If I type lau in the TextField it is accepted and I would rather the answer was false until a whole word is matched
Here is the file limited.txt I use in my project. Each word is on a separate line:
appetitive
appetitiveness
appetitost
appetize
appetized
appetizement
appetizer
appetizers
appetizing
appetizingly
appinite
appius
appl
applanate
applanation
applaud
applaudable
applaudably
applauded
applauder
applauders
applauding
applaudingly
applauds
applause
applauses
applausive
applausively
apple
appleberry
appleblossom
applecart
appled
appledrane
appledrone
applegrower
applejack
applejohn
applemonger
Thanks for your help
import SwiftUI
struct ContentView: View{
#ObservedObject var textFileStringContent: TexFileReader
#State private var text = ""
var body: some View{
VStack {
TextField("please type the word to check", text: $text)
// so that it does not matter if user capitalises a word
if textFileStringContent.data.contains(self.text.lowercased()) {
Text("part of it exists")
// I tried to code it in here but to no avail
// if it is a whole word {
// Text("congratulations it does exist")
// }
} else if !text.isEmpty {
Text("sorry no such word")
}
}.padding().font(.headline)
.navigationBarTitle("Word checker")
}
}
class TexFileReader: ObservableObject {
#Published var data: String = ""
init() { self.load(file: "limited") }
func load(file: String) {
if let filepath = Bundle.main.path(forResource: file, ofType: "txt") {
do {
let contents = try String(contentsOfFile: filepath)
DispatchQueue.main.async {
self.data = contents
print(self.data.contains("lau"))
// this prints true even if lau is not a whole word
// applaud
// applaudable
// applaudably
// applauded
// applauder
// applauders
// applauding
// applaudingly
// applauds
// applause
// applauses
// applausive
// applausively
// but present in each of these
// I need to make sure that the match is a whole word not just part of one
}
} catch let error as NSError {
print(error.localizedDescription)
}
} else {
print("File not found")
}
}
}
A possible way is to search with Regular Expression and the word boundary specifier \\b
if textFileStringContent.data.range(of: "\\b\(self.text)\\b", options: [.caseInsensitive, .regularExpression]) != nil {
You may check if it ends with a newline separator in your text file:
let textWithNewline = self.text.lowercased() + "\n"
if textFileStringContent.data.contains(textWithNewline) {
// it is a whole word
}
Foundation contains a language analysis engine NSLinguisticTagger which can do many things including finding specific words with locale sensitivity.
A simple implementation of what you're trying to do is:
//extension via https://stackoverflow.com/questions/15062458/shortcut-to-generate-an-nsrange-for-entire-length-of-nsstring/56391610#56391610
extension String {
func range(from nsRange: NSRange) -> Range<String.Index>? {
return Range(nsRange, in: self)
}
}
var tagger = NSLinguisticTagger(tagSchemes: [NSLinguisticTagScheme.tokenType], options: 0)
let baddata = """
applaud
applaudable
applaudably
applauded
applauder
applauders catlau
applauding
"""
let gooddata = """
applaud
applaudable
applaudably
applauded
applauder
applauders lau catlau
applauding
"""
var foundLau = false
tagger.string = baddata
tagger.enumerateTags(in: NSRange(location: 0, length: baddata.count), scheme: .tokenType, options: [.omitWhitespace]) { tag, tokenRange, _, _ in
if tag != nil, let range = baddata.range(from: tokenRange) {
let fragment = baddata[range]
if fragment.lowercased() == "lau" {
foundLau = true
}
}
}
print("found \"lau\" in baddata =", foundLau ? "true":"false")
tagger.string = gooddata
tagger.enumerateTags(in: NSRange(location: 0, length: gooddata.count), scheme: .tokenType, options: [.omitWhitespace]) { tag, tokenRange, _, _ in
if tag != nil, let range = gooddata.range(from: tokenRange) {
let fragment = gooddata[range]
if fragment.lowercased() == "lau" {
foundLau = true
}
}
}
print("found \"lau\" in gooddata =", foundLau ? "true":"false")
enumerateTags returns an NSRange which can be converted to Range for general Swift use.
I program an "encryption app" that encrypts texts in a simple way. This is the code:
let smallLetters: String = "abcdefghijklmnopqrstuvwxyz"
let bigLetters: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
let input: String = encryptTextField.text!
encryptTextField.text! = ""
var value: String = ""
for c in input {
let otherChars: String = " !§$%&/()=?´`#+'*-_.:,;<>^°#€{}≠¿|][¢¶“¡≤≈ç√∫~µ#€öäüÜÖÄ"
for i in otherChars {
if c == i {
value += String(i)
print("i")
}
}
var dCount: Int = 0
var eCount: Int = 0
var dFound: Bool = false
var eFound: Bool = false
for d in smallLetters {
if !dFound {
if c == d {
dFound.toggle()
let y: Int = smallCount - dCount
var z: Int = 1
for i in smallLetters {
if y == z {
print("[\(c) -> \(i)]")
value += String(i)
}
z += 1
}
} else {
dCount += 1
}
}
}
for e in bigLetters {
if !eFound {
if c == e {
eFound.toggle()
let y: Int = bigCount - eCount
var z: Int = 1
for i in bigLetters {
if y == z {
print("[\(c) -> \(i)]")
value += String(i)
}
z += 1
}
} else {
eCount += 1
}
}
}
}
let maximumChars: Int = 15
var string: String = ""
var i: Int = 1
var b: Bool = false
for c in value {
if !b {
if i <= maximumChars {
string += String(c)
} else {
string += "..."
b = true
}
i += 1
}
}
let alert: UIAlertController = UIAlertController(title: "Encoded!", message: "Your input is now encrypted / decrypted.", preferredStyle: UIAlertController.Style.alert)
let cancel: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.cancel, handler: { (alert: UIAlertAction!) in
self.encryptTextField.text! = value
print("OK")
self.encryptTextField.placeholder = "Enter a text to encrypt..."
})
let copy: UIAlertAction = UIAlertAction(title: "Copy!", style: UIAlertAction.Style.default, handler: { (alert: UIAlertAction!) in
print("Copy!")
let pasteboard: UIPasteboard = UIPasteboard.general
pasteboard.string! = value
self.encryptTextField.placeholder = "'" + string + "' was copied!"
})
alert.addAction(cancel)
alert.addAction(copy)
present(alert, animated: true, completion: nil)
So that I can quickly insert the encrypted text into another app (e.g. WhatsApp) I use this code (simplified):
let pasteboard: UIPasteboard = UIPasteboard.general
pasteboard.string! = "Hello World!"
The code works in the simulator: The encrypted text is copied and I can paste it with a double click (see picture).
Paste encrypted text with a double click
But when I run the app on my mobile phone (iPhone 8), the app crashes at this point!
Does anyone know a solution or at least know why?
The solution here is to not force unwrap optional variables in your code.
I highly recommend to read more about Optionals in Swift here.
let input: String = encryptTextField.text!
The text in a UITextField can be nil. The data type is a String optional(String?)
Instead of the above, use if let statements to safely unwrap optionals!
if let input = encryptTextField.text{
// Your logic/code to process input as String goes here
}
Similarly, You can remove the force unwrapping("!") done here.
1. encryptTextField.text! = "". Just use encryptTextField.text = ""
2. pasteboard.string! = value You can remove the force unwrapping("!") done here.
3. pasteboard.string! = "Hello World!"
Basically only force unwrap variables if you for sure know that the value that the optional variable holds is not nil!
Force unwrapping optional variables that hold a nil value will crash you app!
I've attempted to research ways to take a given word and calculate the number of possible anagrams a user can make from that word eg an 8 letter word such as snowbanks has 5 eight letter possibilities, 25 seven letter possibilities, etc (those are made up numbers). My initial plan would be to iterate over a dictionary list and check each of the words to see if it is an anagram of the word in question as I've seen suggested in other places.
Rearrange Letters from Array and check if arrangement is in array
seemed very promising, except that it is in objective C and when I tried to convert it to Swift using Swiftify I couldn't get it to work as shown below:
func findAnagrams() -> Set<AnyHashable>? {
let nineCharacters = [unichar](repeating: 0, count: 8)
let anagramKey = self.anagramKey()
// make sure this word is not too long/short.
if anagramKey == nil {
return nil
}
(anagramKey as NSString?)?.getCharacters(nineCharacters, range: NSRange)
let middleCharPos = Int((anagramKey as NSString?)?.range(of: (self as NSString).substring(with: NSRange)).location ?? 0)
var anagrams = Set<AnyHashable>()
// 0x1ff means first 9 bits set: one for each character
for i in 0...0x1ff {
// skip permutations that do not contain the middle letter
if (i & (1 << middleCharPos)) == 0 {
continue
}
var length: Int = 0
var permutation = [unichar](repeating: 0, count: 9)
for bit in 0...9 {
if true {
permutation[length] = nineCharacters[bit]
length += 1
}
}
if length < 4 {
continue
}
let permutationString = String(permutation)
let matchingAnagrams = String.anagramMap()[permutationString] as? [Any]
for word: String? in matchingAnagrams {
anagrams.insert(word)
}
}
return anagrams
}
class func anagramMap() -> [AnyHashable: Any]? {
var anagramMap: [AnyHashable: Any]
if anagramMap != nil {
return anagramMap
}
// this file is present on Mac OS and other unix variants
let allWords = try? String(contentsOfFile: "/usr/share/dict/words", encoding: .utf8)
var map = [AnyHashable: Any]()
autoreleasepool {
allWords?.enumerateLines(invoking: {(_ word: String?, _ stop: UnsafeMutablePointer<ObjCBool>?) -> Void in
let key = word?.anagramKey()
if key == nil {
return
}
var keyWords = map[key] as? [AnyHashable]
if keyWords == nil {
keyWords = [AnyHashable]()
map[key] = keyWords
}
if let aWord = word {
keyWords?.append(aWord)
}
})
}
anagramMap = map
return anagramMap
}
func anagramKey() -> String? {
let lowercaseWord = word.lowercased()
// make sure to take the length *after* lowercase. it might change!
let length: Int = lowercaseWord.count
// in this case we're only interested in anagrams 4 - 9 characters long
if length < 3 || length > 9 {
return nil
}
let sortedWord = [unichar](repeating: 0, count: length)
(lowercaseWord as NSString).getCharacters(sortedWord, range: NSRange)
qsort_b(sortedWord, length, MemoryLayout<unichar>.size, {(_ aPtr: UnsafeRawPointer?, _ bPtr: UnsafeRawPointer?) -> Int in
let a = Int(unichar(aPtr))
let b = Int(unichar(bPtr))
return b - a
})
return String(describing: sortedWord)
}
func isReal(word: String) -> Bool {
let checker = UITextChecker()
let range = NSMakeRange(0, word.utf16.count)
let misspelledRange = checker.rangeOfMisspelledWord(in: word, range: range, startingAt: 0, wrap: false, language: "en")
return misspelledRange.location == NSNotFound
}
}
I've also tried the following in an attempt to just produce a list of words that I could iterate over to check for anagrams (I have working code that checks guesses vs the main word to check for anagrams) but I wasn't able to get them to work, possibly because they require a file to be copied to the app, since I was under the impression that the phone has a dictionary preloaded that I could use for words (although I may be mistaken):
var allTheWords = try? String(contentsOfFile: "/usr/share/dict/words", encoding: .utf8)
for line: String? in allTheWords?.components(separatedBy: "\n") ?? [String?]() {
print("\(line ?? "")")
print("Double Fail \(allTheWords)")
}
and
if let wordsFilePath = Bundle.main.path(forResource: "dict", ofType: nil) {
do {
let wordsString = try String(contentsOfFile: wordsFilePath)
let wordLines = wordsString.components(separatedBy: NSCharacterSet.newlines)
let randomLine = wordLines[Int(arc4random_uniform(UInt32(wordLines.count)))]
print(randomLine)
} catch { // contentsOfFile throws an error
print("Error: \(error)")
}
}
}
I looked at UIReferenceLibraryViewController as well in an attempt to use it to produce a list of words instead of defining a selected word, but the following isn't a valid option.
let words = UIReferenceLibraryViewController.enumerated
Any assistance would be greatly appreciated!
I've been stuck on this for the past couple days. I've made progress on other things in my project but this is holding me back, now i'm at the point where I need to somehow overcome this. Anyways, I've posted a question before about this but I guess I need to be way more specific.
My issue is I need to be able to search through this .CSV file using the barcode and return all the information regarding it. I've done this successfully in c++ but don't know why i'm having so much difficulties in swift.
This is a part of the file (has about 150 in total):
TITLE,SKU,PRICE,Barcode,WEIGHT,BOX HEIGHT,BOX W_IDTH,BOX LENGTH
"No.27 Grapefruit Triple Scented Soy Candle, 7oz",EBWC-10027,72,6933083850573,4,14.8,29.1,20.4
"No.18 Green Tea Jasmine Triple Scented Soy Candle, 7oz",EBWC-10018,72,6933083850580,4,14.8,29.1,20.4
Here is what i've done:
static func searchCode(codeNumber: String) -> [(title:String, sku:String, price:String, barcodeNum:String, weight:String, box_height:String, box_width:String, box_length:String )]?
{
//let test = "6933083850771"
let characterNumber = codeNumber.characters.count
//var dataArray = [Data]()
let barcodeNumber = codeNumber
if let remoteURL = NSURL(string: downloadconstants.URLConstants.productfile ){
while let line: String = barcodeNumber{
// Load the CSV file and parse it
let delimiter = ","
var items:[(title:String, sku:String, price:String, barcodeNum:String, weight:String, box_height:String, box_width:String, box_length:String )]?
do {
let content = try String(codeNumber)
print(content)
items = []
let lines:[String] = content.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) as [String]
for line in lines {
var values:[String] = []
if line != "" {
// For a line with double quotes
// we use NSScanner to perform the parsing
if line.rangeOfString("\"") != nil {
var textToScan:String = line
var value:NSString?
var textScanner:NSScanner = NSScanner(string: textToScan)
while textScanner.string != "" {
if (textScanner.string as NSString).substringToIndex(1) == "\"" {
textScanner.scanLocation += 1
textScanner.scanUpToString("\"", intoString: &value)
textScanner.scanLocation += 1
} else {
textScanner.scanUpToString(delimiter, intoString: &value)
}
// Store the value into the values array
values.append(value as! String)
// Retrieve the unscanned remainder of the string
if textScanner.scanLocation < textScanner.string.characters.count {
textToScan = (textScanner.string as NSString).substringFromIndex(textScanner.scanLocation + 1)
} else {
textToScan = ""
}
textScanner = NSScanner(string: textToScan)
}
// For a line without double quotes, we can simply separate the string
// by using the delimiter (e.g. comma)
} else {
values = line.componentsSeparatedByString(delimiter)
}
// Put the values into the tuple and add it to the items array
let item = (title: values[0], sku: values[1], price: values[2], barcodeNum: values[3], weight: values[4], box_height: values[5], box_width: values[6], box_length: values[7])
items?.append(item)
}
}
} catch {
print(error)
}
}
}
//Error: Use of unresolved identifier "items"
return items
}
I'm getting an error on the return. I've read online a bit, but no luck. Also for what I need my function to do what should be changed to make it work?