Simple textfield validator using Reactive Cocoa - swift

I am testing ReactiveCocoa in a playground. I would like to test for the validity of a string before performing a network request.
I am not sure how to combine the textfield signal and the validation signal tho. The part of the code that is marked [??] => what is the right way to do that filter?
var textField = UITextField()
textField.text = "http://localhost:3000/users.json"
let searchStringsProducer = textField.rac_textSignal().toSignalProducer()
let searchStringValidProducer = searchStringsSignal.toSignalProducer().map{
text in
(text as! String).containsString("http")
}
// this produces a ReactiveCocoa.SignalProducer<(Optional<AnyObject>, Bool), NSError>.
let searchStringCombined = combineLatest(searchStringsProducer, searchStringValidProducer)
// turns the simple search results into a signal
let searchResults = searchStringCombined
// [??] i would like to ONLY execute on the rest of the actions if it is valid
.map{ // what do i do here before passing on to the network API }
.flatMap(FlattenStrategy.Latest) {
latestStr -> SignalProducer<[AnyObject], NSError> in
return requestJSON(latestStr, parameters: nil)
.flatMapError { error in
print("Network error occurred: \(error)")
return SignalProducer.empty
}
}.observeOn(uis)

let searchStringValidProducer = searchStringsSignal.toSignalProducer().map { text in
(text as! String).containsString("http")
}
let searchStringCombined = combineLatest(searchStringsProducer, searchStringValidProducer)
This pattern is troubling, because when searchStringsProducer sends a value, searchStringCombined will send two values -- one for the new string, and one for the new boolean. It would be neater to define this signal like this:
let searchStringCombined = searchStringsSignal.toSignalProducer().map { text in
(text, (text as! String).containsString("http"))
}
Which gives you the same result.
But you don't need to define searchStringCombined at all, unless you're using it elsewhere. You should be able to get by with a simple filter:
searchStringsSignal.toSignalProducer().filter({ text in
(text as! String).containsString("http")
}).map({
/* whatever goes here */
}).flatMap(FlattenStrategy.Latest) {
/* perform network request */
}
filter is kind of like map, in that it takes a function and returns a signal. But it only lets some values through, allowing you to only make network requests based on the valid inputs.

Related

Can't get data returned from dataTask()

For one week I have been trying to get a string returned from dataTask().
I already read a lot here on StackOverFlow and also from serval sites where they tackle this topic. For example, this one. So I already understand that it's that the dataTask doesn't directly return values, cause it happens on different threads and so on. I also read about closures and completion handlers. I really got the feeling that I actually already got a little clue what this is about. But I can't get it to work.
So this is my code. I just post the whole code so no-one needs to worry that the problem sticks in a part which I don't show. Everything is working fine until I try to return a value and save it for example in a variable:
func requestOGD(code gtin: String, completion: #escaping (_ result: String) -> String) {
// MARK: Properties
var answerList: [String.SubSequence] = []
var answerDic: [String:String] = [:]
var product_name = String()
var producer = String()
// Set up the URL request
let ogdAPI = String("http://opengtindb.org/?ean=\(gtin)&cmd=query&queryid=400000000")
guard let url = URL(string: ogdAPI) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result, which is String. It willbecome split and placed in a dictionary
do {
let answer = (String(decoding: responseData, as: UTF8.self))
answerList = answer.split(separator: "\n")
for entry in answerList {
let entry1 = entry.split(separator: "=")
if entry1.count > 1 {
let foo = String(entry1[0])
let bar = String(entry1[1])
answerDic[foo] = "\(bar)"
}
}
if answerDic["error"] == "0" {
product_name = answerDic["detailname"]!
producer = answerDic["vendor"]!
completion(product_name)
} else {
print("Error-Code der Seite lautet: \(String(describing: answerDic["error"]))")
return
}
}
}
task.resume()
Here I call my function, and no worries, I also tried to directly return it to the var foo, also doesn't work The value only exists within the closure:
// Configure the cell...
var foo:String = ""
requestOGD(code: listOfCodes[indexPath.row]) { (result: String) in
print(result)
foo = result
return result
}
print("Foo:", foo)
cell.textLabel?.text = self.listOfCodes[indexPath.row] + ""
return cell
}
So my problem is, I have the feeling, that I'm not able to get a value out of a http-request.
You used a completion handler in your call to requestOGD:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
// result comes back here
}
But then you tried to capture and return that result:
foo = result
return result
So you're making the same mistake here that you tried to avoid making by having the completion handler in the first place. The call to that completion handler is itself asynchronous. So you face the same issue again. If you want to extract result at this point, you would need another completion handler.
To put it in simple terms, this is the order of operations:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
foo = result // 2
}
print("Foo:", foo) // 1
You are printing foo before the asynchronous code runs and has a chance to set foo in the first place.
In the larger context: You cannot use any asynchronously gathered material in cellForRowAt. The cell is returned before the information is gathered. That's what asynchronous means. You can't work around that by piling on further levels of asynchronicity. You have to change your entire strategy.

Append multiple VNCoreMLModel ARKit and CoreML

I'm a noob and I don't really know how can I happened multiple CoreML model to the VNCoreMLRequest.
With the code below is just using one model but I want to append also another model (visionModel2 on the example below). Can anyone help me? Thank you!
private func performVisionRequest(pixelBuffer: CVPixelBuffer){
let visionModel = try! VNCoreMLModel(for: self.iFaceModel.model)
let visionModel2 = try! VNCoreMLModel(for: self.ageModel.model)
let request = VNCoreMLRequest(model: visionModel){ request, error in
if error != nil {
return
}
guard let observations = request.results else {
return
}
let observation = observations.first as! VNClassificationObservation
print("Name \(observation.identifier) and confidence is \(observation.confidence)")
DispatchQueue.main.async {
if observation.confidence.isLess(than: 0.04) {
self.displayPredictions(text: "Not recognized")
print("Hidden")
}else {
self.displayPredictions(text: observation.identifier)
}
}
}
To evaluate an image using multiple ML models, you’ll need to perform multiple requests. For example:
let faceModelRequest = VNCoreMLRequest(model: visionModel)
let ageModelRequest = VNCoreMLRequest(model: visionModel2)
let handler = VNImageRequestHandler( /* my image and options */ )
handler.perform([faceModelRequest, ageModelRequest])
guard let faceResults = faceModelRequest.results as? [VNClassificationObservation],
let ageResults = ageModelRequest.results as? [VNClassificationObservation]
else { /*handle errors from each request */ }
(Yes, you can run Vision requests without a completion handler and then collect the results from multiple requests. Might want to check prefersBackgroundProcessing on the requests and dispatch everything to a background queue yourself, though.)
After that, you probably want to iterate the results from both requests together. Here’s a handy way you could do that with Swift standard library sequence functions, but it assumes that both models return information about the same faces in the same order:
for (faceObservation, ageObservation) in zip (faceResults, ageResults) {
print(“face \(faceObservation.classification) confidence \(faceObservation.confidence)”)
print(“age \(ageObservation.classification) confidence \(ageObservation.confidence)”)
// whatever else you want to do with results...
}
Disclaimer: Code written in StackExchange iOS app, not tested. But it’s at least a sketch of what you’re probably looking for — tweak as needed.

Method call asks for method as parameter

I'm pretty new to swift so this might be a really simple question, but I am trying to create a method that returns a list upon completion but when I try to call the method, it says I am missing the escaping parameter which I do not know how to satisfy.
Here is the method:
func fillFromFile(completionBlock: #escaping ([Asset_Content]) -> ()) {
let url = "URL STRING"
LoadJSONFile(from: url) { (result) in
// The code inside this block would be called when LoadJSONFile is completed. this could happen very quickly, or could take a long time
//.map is an easier way to transform/iterate over an array
var newContentArray = [Asset_Content]()
for json in result{
let category = json["BIGCATEGORY"] as? String
let diagnosis = json["DIAGNOSIS"] as? String
let perspective = json["PERSPECTIVE"] as? String
let name = json["NAME"] as? String
let title = json["Title"] as? String
let UnparsedTags = json["TAGS"] as? String
let filename = json["FILENAME"] as? String
let tagArray = UnparsedTags?.characters.split(separator: ",")
for tag in tagArray!{
if(!self.ListOfTags.contains(String(tag))){
self.ListOfTags.append(String(tag))
}
}
let asset = Asset_Content(category!, diagnosis!, perspective!, name!, title!, filename!)
// This is a return to the map closure. We are still in the LoadJSONFile completion block
newContentArray.append(asset)
}
print("return count ", newContentArray.count)
// This is the point at which the passed completion block is called.
completionBlock(newContentArray)
}
}
here is the method call:
self.ListOfFiles = fillFromFile()
and the error is "Missing argument for parameter 'completionblock' in call"
The way you expect the response of a method with completionBlock is like this:
fillFromFile { (response) in
self.ListOfFiles = response
}
Like this you are setting your ´ListOfFiles´ variable, with the new variable that comes in the method.
In the return of your function you should have a DispatchQueue
DispatchQueue.main.async {
completionBlock(newContentArray)
}
Notice that the fillFromFile function doesn't return anything. It's an asynchronous function. This means that it does its work independently of the main control flow of the thread it was called from. It'll return (nothing) almost immediately, and perform its work some unknown time thereafter.
To obtain the result of this function, you're expected to given a completion handler. This is a closure that will be called by the code when it eventually completes its work. As a parameter to this closure, it will pass in the result of the work (an Array<Asset_Content>).
Here's simple example of how to satisfy this method signature:
fillFromFile { (response) in
print(response)
}
I suggest you read the language guide, especially its section on closures.

Result of call is unused

Right below the second comment, I receive an error of "Result of call to 'taskForDeleteMethod' is unused. Why is this when I use the results and error in the closure following the call?
func deleteSession(_ completionHandlerForDeleteSession: #escaping (_ success: Bool, _ error: NSError?) -> Void) {
/* 1. Specify parameters, method (if has {key}), and HTTP body (if POST) */
// There are none...
/* 2. Make the request */
taskForDELETEMethod { (results, error) in
/* 3. Send the desired value(s) to completion handler */
if let error = error {
print("Post error: \(error)")
completionHandlerForDeleteSession(false, error)
} else {
guard let session = results![JSONKeys.session] as? [String: AnyObject] else {
print("No key '\(JSONKeys.session)' in \(results)")
return
}
if let id = session[JSONKeys.id] as? String {
print("logout id: \(id)")
completionHandlerForDeleteSession(true, nil)
}
}
}
}
In earlier swift versions, you need not bother about the return value of a method. You may store it in any variable snd use it later or you may ignore it completely. Neither it gave any error nor a warning.
But in swift 3.0 you need to specify whether you want to ignore the returned value or use it.
1. If you want to use the returned value, you can create a variable/constant and store the value in it, i.e
let value = taskForDELETEMethod {
// Your code goes here
}
2. If you want to ignore the returned value, you can use _ ,i.e
let _ = taskForDELETEMethod {
// Your code goes here
}
You are confusing the results variable, which is, indeed, used inside the closure, and the result of the taskForDELETEMethod call itself, which is NSURLSessionDataTask object.
From the examples of using taskForDELETEMethod that I was able to find online it looks like it is perfectly OK to ignore the return value, so you can avoid this warning by assigning the result to _ variable, i.e.
let _ = taskForDELETEMethod {
... // The rest of your code goes here
}

how to send through multiple dictionaries/ multiple 'application updates' with swift 2.2 watch connectivity

How can I send through several 'application updates' from my phone to my watch, (such as several different values from an array) with Watch Connectivity?
My application update worked successfully to send through the numberItem value from the selected cell in my table view, but I would like to also send through the userid value from the selected cell array.
Right now, it only recognizes one value, and doesn't update the other value, but displays 'please retry' as my label text.
How can I send through two or more application updates, for other additional values (such as userid, and username).
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let numberItem = number[indexPath.row]
print("tableview select #:")
print(numberItem)
do {
try WatchSessionManager.sharedManager.updateApplicationContext(["number" : numberItem])
} catch {
let alertController = UIAlertController(title: "Oops!", message: "Looks like your \(numberItem) got stuck on the way! Please send again!", preferredStyle: .Alert)
presentViewController(alertController, animated: true, completion: nil)
}
let uidItem = 15
//let uidDict = ["uidValue":uidItem]
print("the send UID is")
//print(uidItem)
do {
try WatchSessionManager.sharedManager.updateApplicationContext(["uidValue" : uidItem])
} catch {
let alertController = UIAlertController(title: "Oops!", message: "Looks like your \(uidItem) got stuck on the way! Please send again!", preferredStyle: .Alert)
presentViewController(alertController, animated: true, completion: nil)
}
}
My datasource.swift file is:
enum Item {
case Number(String)
case uidValue(String)
case Unknown
}
init(data: [String : AnyObject]) {
if let numberItem = data["number"] as? String {
item = Item.Number(numberItem)
print("enum item is")
print(numberItem)
} else if let uidItem = data["uidValue"] as? String {
item = Item.uidValue(uidItem)
print("enum item is")
print(uidItem)
} else {
item = Item.Unknown
}
}
and my interface controller on the watch (connected to my data labels) includes:
func dataSourceDidUpdate(dataSource: DataSource) {
switch dataSource.item {
// the first application update- commented out to try the 2nd update
//case .Number(let numberItem):
// titleLabel.setText(numberItem)
// print(numberItem)
// the second application update
case .uidValue(let uidItem):
uidLabel.setText(uidItem)
print(uidItem)
case .Unknown:
nidLabel.setText("please retry")
default:
print("default")
}
}
You can't send the items separately, as updateApplicationContext would have replaced any earlier application context data with the most recent data. This is briefly mentioned in two different spots in the documentation:
This method overwrites the previous data dictionary, ...
This method replaces the previous dictionary that was set, ...
Naturally, Apple optimizes the whole process for energy/memory efficiency. In this case, if the earlier application context data was still in the queue to be transmitted when the second data was queued for transmission, the earlier data can be discarded to save from having to unnecessarily transmit/store it. Your watch wouldn't even have received the first data.
Since your watch would have only received one of the two pieces of data, this explains why you'd see "please retry" when you checked the received dictionary for one key, but it only contained the data for the other key.
How to transmit more than one item at once
Include both items in the same dictionary, and transmit that dictionary using a single transfer.
let data = ["number" : numberItem, "uidValue" : uidItem]
try WatchSessionManager.sharedManager.updateApplicationContext(data)
...
On the watch side, you simply can update the title label and uid label at the same time, instead of conditionally updating only one or the other.
if let numberItem = data["number"] as? String {
titleLabel.setText(numberItem)
}
if let uidItem = data["uidValue"] as? String {
uidLabel.setText(uidItem)
}

Categories