Swift If-Statement Array Manipulation Scope - swift

I have a basic array that I'd like to manipulate. It seems that within the brackets of my if statement I can successfully manipulate the array (the right side of the screen shows that value has changed); however, once I leave the if statement, the changes seem to go away (the right side of the screen shows the original value, not the changed one). Thank you for your help!
var originalInfo = """
This
Is
A
Test
String
"""
// split string
var SplitInfo = originalInfo.split(separator: "\n")
// Manipulate Array
if SplitInfo[1].hasPrefix("I") == true {
SplitInfo[1].replacingOccurrences(of: "s", with: "s not")
}
SplitInfo[1]

The method replaceOccurences(of:with:) returns a String with the new value, so you'll need to assign the new value to the variable
SplitInfo[1] = SplitInfo[1].replacingOccurrences(of: "s", with: "s not")
Only then will you see the change. If you click on the method and check the Quick Help section in the right pane of Xcode, you'll see
Range<String.Index>? = default) -> String
the -> String means it returns a String, so you'll need to assign that to something.
If you're trying to maintain String then divide your string like this
var SplitInfo = originalInfo.components(separatedBy: "\n")
This will return [String] instead of substrings.
Hope this helps.
Edit based on OP's comment *

Related

Is there a faster method to find words beginning with string inside a string?

I have a field called keywords on Core Data that stores keywords separated by spaces, like:
car nascar race daytona crash
I have a list populated by core data. Every element on that list has keywords.
I have a search field on that view.
I want that list to be filtered as the user types.
If the user types c the app will check elements that have keywords beginning with c. In that case, the element mentioned above will be shown because it has car and crash, both beginning with c.
In order to check that, I created this extension
extension String {
func containsWordStartingWith(insensitive searchWord: String) -> Bool {
let lowercaseSelf = self.lowercased().trimmingCharacters(in: .whitespaces)
let lowercaseSearch = searchWord.lowercased().trimmingCharacters(in: .whitespaces)
let array = lowercaseSelf.components(separatedBy: " ")
return array.contains(where: {$0.hasPrefix(lowercaseSearch)})
}
}
This works but is slow as hell and typing characters on the search bar makes the app stall.
How can I improve that with something faster?
First thing I would do is split the single keywords string into a Set of actual keywords. If possible you should even store it in Core Data that way, so there's no need for a split step.
let keywords = "car nascar race daytona crash"
let keywordSet = Set(keywords.split(separator: " "))
Now the utility method you want is trivial and fast:
func keywordSet(_ set : Set<Substring>, containsWordStartingWith s: Substring) -> Bool {
for keyword in set {
if keyword.hasPrefix(s) { return true }
}
return false
}
Testing:
keywordSet(keywordSet, containsWordStartingWith:"c")

swift 5.1 evaluate a closure in the wrong way

I'm having an issue with evaluation of one line of code
if i break it down to two lines, it's working , but in one line of code, it's just evaluate in a 'new' to a 'wrong' way.
my main reason for asking this question, is not to solve it, I know I can use parenthesis to solve it, and break it to Two line, but don't want to solve it, I just want to know why its evaluated like this , and if there's a solution for this : some setting to patch , in Order THAT it will work in ONE LINE OF CODE :
Heres the code that working in Two lines
Heres the code that trying to do the same thing, but rise an error as you can see:
full code of both working and not working :
class ClosuresStack {
var dic = Dictionary<String,(()->String)->String >()
subscript(_ str:String)-> (()->String)->String {
get {
return dic[str]!
}
set {
dic[str] = newValue
}
}
}
func createClosuresStak() -> ClosuresStack {
let cs = ClosuresStack()
func takesAClosureReturnA_string(_ closure:()->String) ->String {
return closure() + " Two"
}
cs["C"] = takesAClosureReturnA_string
return cs
}
let c = createClosuresStak()["C"]
let str = c{"One"}
print(str) // print: One Two
let c = createClosuresStak()["C"]{"One"} // error -->
now, I want to somehow understand how to change it that it will work in ONE LINE OF CODE : meaning that the evaluation of 'createClosuresStak()["C"]{"One"}' will create a closure after ["C"] , and then from that point writing the {"One"}
will make it a full evaluate of the line :
let c = createClosuresStak()["C"]{"One"}
making 'c' a String
if that's not possible, I need to know it Too , tnx :)
UPDATE
tnx for the comments , its help me understand the problem more clearly :
1) im understanding that the createClosuresStak()["C"]{"One"}
acutely trying to add the string 'One' as another parameter to the sub script , and there for the error from the compiler was that is cannot subscript (String,()->String} , 'C' as the string inside the [] , and the other parameter {"One"} -> BUT , isn't that some kind of a bug?, been that i'm using [] ,Cleary the compiler need to 'understand' that I want to subscript a String, also by power of inferring that swift has,
2) now I'm still trying to get that syntax to work as it is so I try to change some things, in order to get it to work :
so I created a function that take a string, and return a dictionary of type : Dictionary<String,()->String>, and then trying so subscript it
and the compiler don't rise an error that way :
func closuresDictionary(_ s:String) -> Dictionary<String,()->String> {
var dic = Dictionary<String,()->String>()
func foo()->String {
return s + " Two"
}
dic["C"] = foo
return dic
}
let c = closuresDictionary("One")["C"]{ "SomeString" }
c is now a closure of type ()->String which does noting with string that I put inside, so the syntax works, but the outcome is not doing anything.
when im changing the return type of the dictionary to a different closure : (String)->String instead of ()->String , im getting the same old error, that I'm trying to subscript a (String,(String)->String)
and I need a function that will take the string inside the {} , and create something from it meaning that I need to subscript to return a closure of (String)->String
its seems like there's no way to do that
im adding two more pictures of my last trying in order to get this line of code in current syntax to work
the wanted syntax working but the outcome is not an outcome not doing any thing with the string inside the {}:
same error, by changing the function to (String)->String
Your example:
let c = createClosuresStak()["C"]{"One"}
is using trailing closure syntax.
Trailing closure syntax works by including the trailing closure as an additional parameter to a function call. Subscripting an array is really a function call under the hood (to a function called subscript), and Swift is trying to pass that closure as a second parameter to the subscripting call, which is what the error is explaining:
Cannot subscript a value of type 'ClosuresStack' with an argument of type '(String, () -> String)'.
In other words, you can't pass both "C" and the closure {"One"} to the subscripting function.
There are at least 3 ways to fix this and still put it on one line:
Option 1: Use an explicit call to pass the closure instead of using trailing closure syntax
Wrap the closure in () to make the call explicit:
let c1 = createClosuresStak()["C"]({"One"})
print(c1)
Option 2: Wrap the createClosureStak()["C"] in parentheses
That lets Swift know the subscripting only gets "C" as a parameter and allows trailing closure syntax to work as expected:
let c2 = (createClosuresStak()["C"]){"One"}
print(c2)
Option 3: Add .self to the result before the trailing closure syntax:
That again finishes the subscripting call and avoids the confusion.
let c3 = createClosuresStak()["C"].self {"One"}
print(c3)
Personally, I would choose option one, because trailing closure syntax is unnecessary syntactic sugar that clearly is not working here.

Swift: Can not use array filter in if let statement condition

Suppose I have an array of user's names
let users = ["Hello", "1212", "12", "Bob", "Rob"]
I want to get the first user whose name length is 2, so I filtered the array and got the first user
if let selected = users.filter{$0.characters.count == 2}.first {
print(selected)
}
This code is throwing a compilation error under swift 2.2
Consecutive statements on a line must be separated by ';'
However, this is working fine though
let selected = users.filter{$0.characters.count == 2}.first
if let selected = selected {
print(selected)
}
Can anyone explain why do I need to store filter result in a separate variable first? Any help would be really appreciated.
You can make this work by putting parentheses around the closure that you're passing to filter:
if let selected = users.filter({$0.characters.count == 2}).first {
print(selected)
}
That is the right way to do it. The trailing closure syntax doesn't work very well sometimes on lines with extra elements. You could also put parentheses around the whole statement:
if let selected = (users.filter {$0.characters.count == 2}.first) {
print(selected)
}
Swift is just having trouble parsing your statement. The parentheses give it help in how to parse the line. You should prefer the first way since the closure is indeed a parameter of filter, so enclosing it in parentheses makes it clear to Swift that you are passing it to filter.

Function returning specified values but structure doesn't append its values

https://github.com/mateo951/ISBN-Vista-Jera-rquica- Github Link
The structure I have is supposed to be appending values after an internet search. The internet search is called within a function and returns two strings and an image. When I try to append the returned values in the structure, the image is saved but strings are nil.
var datosLibros = [bookData]()
#IBAction func Search(sender: UITextField) {
let (title1, author1, cover1) = (internetSearch(sender.text!))
let libro = bookData(title: title1, author: author1,image:cover1)
datosLibros.append(libro)
print(datosLibros)
}
The saved structured that is printed to the console is the following:
bookData(title: "", author: "", image: <UIImage: 0x7f851a57fbf0>, {0, 0})
Structure:
struct bookData {
var title: String
var author: String
var image: UIImage
init(title: String, author: String, image: UIImage) {
self.title = title
self.author = author
self.image = image
}
}
Thanks in advanced for any advice of help provided. I'm new to swift so there are a lot of stuff uncovered.
The problem is not with the code you posted but with internetSearch.
But before I explain what is going on there, just a quick note about Swift structs. Structs come with one free initializer that takes as its parameters one value for each stored property defined on the struct. Argument labels correspond to the variable labels.
So for your struct bookData (which really should be BookData since types should be capitalized), you do not need to include that initializer you wrote because it will be automatically provided for you as long as you do not create any additional BookData initializers.
Now for the reason your results are not what you expect. Your Strings are not coming back as nil. Instead, they are coming back as empty Strings, or "". In Swift, "" is very different from nil, which means a complete absence of a value. So your Strings are indeed there, they are just empty.
Okay, our Strings are coming back empty. How about our image? No, our image is not coming back either. You thought it was because you saw a UIImage reference printed in the console, but if you look closer you will notice it is a bogus image. Notice "{0, 0}" after the memory address for the instance. As far as I'm aware, this means the image has a size of 0 x 0. How many useful images do you know that have a size of 0 x 0?
So now we have discovered that our Strings are coming back empty and effectively so is our image. What is going on here?
Well, in your implementation of internetSearch I found on GitHub, this is the first thing you do:
var bookTitle = String()
var bookAuthor = String()
var bookCover = UIImage()
Naturally, you do this so that you have some variables of the correct types ready to plop in some actual results if you find them. Just for fun, let's see what the result of the code above would be if there were no results.
Well, the initializer for String that accepts no parameters results in an empty String being created.
Okay, how about our image. While the documentation for UIImage does not even mention an initializer that takes no parameters, it does inherit one from NSObject and it turns out that it will just create an empty image object.
So we now have discovered that what internetSearch is returning is actually the same as what it would be if there were no results. Assuming you are searching for something that you know exists, there must be a problem with the search logic, right? Not necessarily. I noticed that your implementation of the rest of internetSearch relies on an NSURLSession that you use like so:
var bookTitle = String()
var bookAuthor = String()
var bookCover = UIImage()
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url) { (data, response, error) -> Void in
// Lots of code that eventually sets the three variables above to a found result
}
task.resume()
return (bookTitle, bookAuthor, bookCover)
That seems fine and dandy, except for the fact that NSURLSession performs its tasks asynchronously! Yes, in parts you even dispatch back to the main queue to perform some tasks, but the closure as a whole is asynchronous. This means that as soon as you call task.resume(), NSURLSession executes that task on its own thread/queue/network and as soon as that task is set up it returns way before it completes. So task.resume() returns almost immediately, before any of your search code in the task actually runs, and especially before it completes.
The runtime then goes to the next line and returns those three variables, just like you told it to. This, of course, is the problem because your internetSearch function is returning those initial empty variables before task has a chance to run asynchronously and set them to helpful values.
Suggesting a fully-functional solution is probably beyond the scope of this already-long answer, but it will require a big change in your implementation detail and you should search around for using data returned by NSURLSession.
One possible solution, without me posting any code, is to have your internetSearch function not return anything, but on completion of the task call a function that would then append the result to an array and print it out, like you show. Please research this concept.
Also, I recommend changing your implementation of internetSearch further by declaring your initial values not as:
var bookTitle = String()
var bookAuthor = String()
var bookCover = UIImage()
…but as:
var bookTitle: String?
var bookAuthor: String?
var bookCover: UIImage?
This way, if you find a result than you can represent it wrapped in an Optional and if not you can represent that as nil, which will automatically be the default value of the variables in the code directly above.

Swift: Remove text from string [duplicate]

I'm very new to Swift; I've spent the morning reading StackOverflow and trying many strategies, in vain, to accomplish the following:
I have a string, say "12345 is your number!"
I want to extract "12345" to a variable.
In Java, I'd do something like:
String myStr = "12345 is your number!";
return myStr.substring(0, myStr.indexOf(" "));
How do I do something similar in Swift? I don't want to hard-code any assumptions about what the ending index will be. It might be 5 characters in, it might not. I just want to take the substring of everything up to the first occurrence of " ", wherever that might be.
The closest I've gotten so far is:
var myMessage = "12345 is your number!"
myMessage.endIndex.advancedBy(myMessage.characters.count - myMessage.characters.indexOf(" "))
but it doesn't compile for reasons I don't fully yet grok("Binary operator '-' cannot be applied to operands of type Distance (aka 'Int') and 'String.CharacterView.Index?'")
Any help on this is appreciated. Thank you.
Something like this should work:
myMessage.substringToIndex(myMessage.characters.indexOf(" ")!)
Note that in this code I force unwrapped the optional. If you're not guaranteed to have that space in the string, it might make more sense to have the index in a optional binding.
With optional binding, it would look something like this:
if let index = myMessage.characters.indexOf(" ") {
let result = myMessage.substringToIndex(index)
}
You can use a regex, try this code:
var myMessage = "12345 is your number!"
if let match = myMessage.rangeOfString("-?\\d+", options: .RegularExpressionSearch) {
print(myMessage.substringWithRange(match)) // 12345
let myNumber = Int(myMessage.substringWithRange(match)) // Then you can initialize a new variable
}
The advantage is that this method extracts only the numbers wherever they are in the String
Hope this help ;)
With Swift 5 you can use:
myStr.prefix(upTo: myStr.firstIndex(of: " ") ?? myStr.startIndex)
You may need to cast it back to String (String(myStr.prefix(upTo: myStr.firstIndex(of: " ") ?? myStr.startIndex))) since it returns a Substring