UISwitch won't let me set default state - ios

I have a custom UICell that contains a UISwitch element. When I render my table, cellForRowAtIndexPath() will check to see if the users preference matches a specific category, if it does they are subscribed, else they are not. I have written some logic to toggle the switch based on this result:
if let userPreferences = user["preferences"] as? Array<AnyObject>
{
if userPreferences[indexPath.row].objectId == game.getGameId() {
prefCell.subscribed?.setOn(false, animated: true)
}
}
I have breakpointed the line where the switch is disabled, and it breaks 9 times out of the current 11 items I retrieve in the userPreferences collection, however none of the switches on my UI are updated. Why is this happening?
EDIT: After some further testing it appears that the if statement is always satisfied, this is extremely strange as in the event that the strings don't match, Swift still claims that they did.
For example if I have a list of the ID's
xyz12345
abc12345
efg12345
And the following loop executed:
if let userPreferences = user["preferences"] as? Array<AnyObject>
{
for preference in userPreferences
{
if preference.objectId == game.getGameId()
{
prefCell.subscribed.on = true;
} else {
prefCell.subscribed.on = false;
}
}
}
The first comparison could be something like preference = xyz12345 and game.getGameId() = abc12345. I'd expect the second case to be invoked as these strings certainly do not match, however the case passes as true and the switch is set, why am I witnessing this behaviour?

Related

Tableview reload not accurate

I have a controller, that allows the user to type in a TextField.
Every time the user types a character, the string in that textfield is compared to an array of strings. If there is a match, the resulting array is displayed in a uitableview.
Here's the code:
func searchAutocompleteEntriesWithSubstring(substring:String){
let SUBSSTRING = substring.uppercased()
autocompleteStrings.removeAll()
for thisSchool in schoolArray{
if(thisSchool.name?.uppercased() .contains(SUBSSTRING))!{
autocompleteStrings.append(thisSchool)
}
}
autocompleteTableView.reloadData()
}
Basically, this works fine. BUT!
If the user types rather fast, the autocompleteTableView displays one or more (empty) rows than there actually are strings in the autocompleteStrings array.
I tried encapsulating the above code in DispatchQueue.main.async {}, but that made things even worse.
I guess it has something to do with NeedsLayout or NeedsDisplay, but I've never really understood the mechanism behind it, and how/where to apply these.
I hope you can advise me
Try this code
func searchAutocompleteEntriesWithSubstring(substring: String) {
let filtered = schoolArray.filter() { ($0.name ?? "").uppercased().range(of: substring.uppercased()) != nil }
autocompleteStrings = filtered.map() { $0.name! }
autocompleteTableView.reloadData()
}
Maybe you need a lock?
1.in a async queue.
2.lock locks.
3.matching and array appending
4.lock unlocks.
5.reload in mainqueue

How to Update uitableview from CloudKit using discoverAllIdentities

So, I am new to cloudKit and to working with multiple threads in general, which I think is the source of the problem here, so if I simply need to research more, please just comment so and I will take that to heart.
Here is my question:
I am working in Swift 3 Xcode 8.1
I have in my view controller this variable:
var contactsNearby: [String:CLLocation]?
Then at the end of ViewDidLoad I call one of my view controllers methods let's call it:
populateContactsNearby()
inside that method I call:
container.discoverAllIdentities(completionHandler: { (identities, error) in
for userIdentity in identities! {
self.container.publicCloudDatabase.fetch(withRecordID: userIdentity.userRecordID!, completionHandler: { (userRecord, error) in
let contactsLocation = userRecord?.object(forKey: "currentLocation")
if self.closeEnough(self.myLocation!, contactLocation: contactsLocation as! CLLocation) {
var contactsName = ""
contactsFirstName = userIdentity.nameComponents?.givenName
if contactsName != "" && contactsLocation != nil {
self.contactsNearby?["\(contactsName)"] = contactsLocation as? CLLocation
}
}
})
}
})
}
I apologize if I am missing or have an extra bracket somewhere. I have omitted some error checking code and so forth in order to get this down to bare-bones. So the goal of all that is to populate my contactsNearby Dictionary with data from CloudKit. A name as the key a location as the value. I want to use that data to populate a tableview. In the above code, the call to closeEnough is a call to another one of my view controllers methods to check if the contact from CloudKit has a location close enough to my user to be relevant to the apps purposes. Also myLocation is a variable that is populated before the segue. It holds the CLLocation of the app users current location.
The Problem:
The if statement:
if contactsName != "" && contactsLocation != nil { }
Appears to succeed. But my view controllers variable:
var contactsNearby: [String:CLLocation]?
Is never populated and I know there is data available in cloudKit.
If it's relevant here is some test code that I have in cellForRowAtIndexPath right now:
let contact = self.contactsNearby?.popFirst()
let name = contact?.key
if name != nil {
cell.textLabel?.text = name
}else {
cell.textLabel?.text = "nothing was there"
}
My rows alway populate with "nothing was there". I have seen answers where people have done CKQueries to update the UI, but in those answers, the user built the query themselves. That seems different from using a CloudKit function like discoverAllIdentities.
I have tried to be as specific as possible in asking this question. If this question could be improved please let me know. I think it's a question that could benefit the community.
Okay, I need to do some more testing, but I think I got it working. Thank you Paulw11 for your comment. It got me on the right track.
As it turns out there were 2 problems.
First, as pointed out I have an asynchronous call inside a for loop. As recommended, I used a dispatchGroup.
Inside the cloudKit call to discoverAllIdentities I declared a dispatchGroup, kind of like so:
var icloudDispatchGroup = DispatchGroup()
Then just inside the for loop that is going to make an async call, I enter the dispatchGroup:
icloudDispatchGroup.enter()
Then just before the end of the publicCloudDatabase.fetch completion handler I call:
icloudDispatchGroup.leave()
and
icloudDispatchGroup.wait()
Which, I believe, I'm still new to this remember, ends the dispatchGroup and causes the current thread to wait until that dispatchGroup finishes before allowing the current thread to continue.
The Above took care of the multithreading issue, but my contactsNearby[String:CLLocation]? Dictionary was still not being populated.
Which leads me to the 2nd problem
At the top of my view controller I declared my Dictionary:
var contactsNearby: [String: CLLocation]?
This declared a dictionary, but does not initialize it, which I don't think I fully realized, so when I attempted to populate it:
self.contactsNearby?["\(contactsName)"] = contactsLocation as? CLLocation
It quietly failed because it is optional and returned nil
So, in viewDidLoad before I even call populateContactsNearby I initialize the dictionary:
contactsNearby = [String:CLLocation]()
This does not make it cease to be an optional, which Swift being strongly typed would not allow, but merely initializes contactsNearby as an optional empty Dictionary.
At least, that is my understanding of what is going on. If anyone has a more elegant solution, I am always trying to improve.
In case you are wondering how I then update the UI, I do so with a property observer on the contactsNearby Dictionary. So the declaration of the dictionary at the top of the view controller looks like this:
var contactsNearby: [String: CLLocation]? {
didSet {
if (contactsNearby?.isEmpty)! || contactsNearby == nil {
return
}else{
DispatchQueue.main.sync {
self.nearbyTableView.reloadData()
}
}
}
}
I suppose I didn't really need to check for empty and nil. So then in cellForRowAtIndexPath I have something kind of like so:
let cell = tableview.dequeueReusableCell(withIdentifier: "nearbyCell", for: indexPath)
if contactsNearby?.isEmpty == false {
let contact = contactsNearby?.popFirst()
cell.textLabel?.text = contact?.key
}else {
cell.textLabel?.text = "Some Placeholder Text Here"
}
return cell
If anyone sees an error in my thinking or sees any of this heading for disaster, feel free to let me know. I still have a lot of testing to do, but I wanted to get back here and let you know what I have found.

How to filter large array in iOS swift 2 for uisearchbar

I am having a UISearchBar with more than 80000 elements in an array and I have to filter this array according to the user input.
But while typing in search view its working very slow means its taking too much time for typing values in keyboard.
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.characters.count == 0 {
searchActive = false
} else {
searchActive = true;
filtered.removeAllObjects()
dispatch_to_background_queue {
for sumber in self.data {
let nameRange: NSRange = sumber.rangeOfString(searchText, options: [NSStringCompareOptions.AnchoredSearch,NSStringCompareOptions.CaseInsensitiveSearch])
if nameRange.location != NSNotFound {
self.filtered.addObject(sumber)
}
}//end of for
self.dispatch_to_main_queue {
/* some code to be executed on the main queue */
self.tableView.reloadData()
}
} //end of dispatch
}
}
func dispatch_to_main_queue(block: dispatch_block_t?) {
dispatch_async(dispatch_get_main_queue(), block!)
}
func dispatch_to_background_queue(block: dispatch_block_t?) {
let q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(q, block!)
}
There are two approaches to combine here for the best result:
First, keep long-running operations off the main (UI) thread
You can dispatch the filtering to a background thread using dispatch_async, or even to a background thread after some delay using dispatch_after.
Second, don't filter the array immediately after every key press
It's a waste of time because usually the user will type several keys before waiting to see what pops up. You want to therefore delay the filtering operation, and only perform it after some small amount of time has passed since the last key press. This is called "debouncing".
Here's a neat way to do all of this in Swift:
func debounce(delay:NSTimeInterval, queue:dispatch_queue_t, action: (()->())) -> (()->()) {
var lastFireTime:dispatch_time_t = 0
let dispatchDelay = Int64(delay * Double(NSEC_PER_SEC))
return {
lastFireTime = dispatch_time(DISPATCH_TIME_NOW,0)
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
dispatchDelay
),
queue) {
let now = dispatch_time(DISPATCH_TIME_NOW,0)
let when = dispatch_time(lastFireTime, dispatchDelay)
if now >= when {
action()
}
}
}
}
class ViewController {
lazy var debouncedFilterArray : () -> () = debounce(0.3, queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), action: self.filterArray)
func filterArray() {
// do filtering here, but don't call this function directly
}
}
The debounce function itself returns a function that when called will exhibit this "debouncing" behaviour, running no more often than the delay interval passed to it.
To use, simply call debouncedFilterArray(). It will in turn call filterArray, but always on a background thread and never more often than every 0.3 seconds.
I want to add a couple of thoughts.
You already seem to do async processing, which is great. It won't make the search faster, but the app keeps responsive. Consider making it stoppable. If the user types three letters, you will queue up three searches and will get the relevant results only after the last run finished. This could be done using some sort of a boolean stop flag that gets checked within the search. If a new search is started, kill the old one first.
Show partial results. The user won't be watching at thousands of cells at once, but only at the first 20 or so. Depending on the order of your input and output, this may be very easy to do and fast as hell.
Build on your previous search. Searching for "Ab" will only be successful if searching for "A" (or "b" for that matter if the search wasn't anchored) was successful at well. So if your last search was a substring from your current search, take the output array of your previous search as an input. Obviously, be careful with stopped searches here.
Check if performance is really as bad. Do you run with optimizations switched on? The debug mode might be considerable slower, but that wouldn't matter.
Where does the data come from? That's a rather huge amount of data to keep around in memory. If it's coming from a database, using database functions might be easier (and most words above still comply).
Still too slow? Index your data set. If you know upfront which elements contain "A", the number of needed searches could drop significantly. And you'd have the results for the first search already
As you're using anchored search, working on a sorted array could give a much better performance characteristic. Just find the first and last element of your search term with binary search and use that range. Maybe without even copying into a new array. This approach puts some workload upfront (maybe before the user even started typing). If your search data is within larger objects, some sort of index tables would do.
You could perform the filtering on a background thread so to leave the main thread (which does manage the UI) responsive.
func filter(list:[String], keyword:String, completion: (filteredList:[String]) -> ()) {
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
dispatch_async(queue) {
let filtered = list.filter { $0.containsString(keyword) }
dispatch_async(dispatch_get_main_queue()) {
completion(filteredList: filtered)
}
}
}
Example
let data = ["dog", "cat", "eagle"]
filtered(data, keyword: "do") { (filteredList) -> () in
// update the UI here
}
80000! That is a lot of data indeed. One solution which could considerably speed things is to shrink the search array after each key stroke AND to cancel the search if many keystrokes are typed in a row while caching the search in case keystrokes are erased. You could combine it with appzYouLife's answer and you would already have a more solid framework. Here is an example of how that could work, the token is necessary so that you update the UI in accordance with the search:
var dataToSearch = [AnyObject]()
var searchCache = NSCache()
var currentSearchToken = 0
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
performSearch(searchText, searchToken: ++currentSearchToken)
}
func performSearch(searchText: String, searchToken: Int) {
if let searchResults = searchCache.objectForKey(searchText) as? [AnyObject] { //If the search is cached, we simply pull the results
guard searchToken == currentSearchToken else {return} //Make sure we don't trigger unwanted UI updates
performListUpdate(searchResults)
return
}
var possiblePreviousSearch = searchText //We're going to see if we can build on any of previous searches
while String(possiblePreviousSearch.characters.dropLast()).characters.count > 0 { //While we still have characters
possiblePreviousSearch = String(possiblePreviousSearch.characters.dropLast()) //Drop the last character of the search string
if let lastSearch = searchCache.objectForKey(possiblePreviousSearch) as? [AnyObject]{ //We found a previous list of results
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
dispatch_async(queue) {
let newResults = lastSearch.filter {object in /**put your conditions here instead of return true*/ return true} //Sort on top of a previous search
self.searchCache.setObject(newResults, forKey: searchText)
guard searchToken == self.currentSearchToken else {return} //We don't want to trigger UI Updates for a previous search
dispatch_async(dispatch_get_main_queue()) {
self.performListUpdate(newResults)
return
}
}
}
}
//If we got to this point, we simply have to search through all the data
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
dispatch_async(queue) {
let newResults = self.dataToSearch.filter {object in /**put your conditions here instead of return true*/ return true} //Sort on top of a previous search
self.searchCache.setObject(newResults, forKey: searchText)
guard searchToken == self.currentSearchToken else {return} //We don't want to trigger UI Updates for a previous search
dispatch_async(dispatch_get_main_queue()) {
self.performListUpdate(newResults)
return
}
}
} //end of perform search
Of course this answer isn't perfect. It assumes your lists can be sorted on top of smaller ones (for example the results of searching for "abc" will be a subset of the results of searching "ab" and "a").
EDIT Combine this with debouncing as shown in another answer and you have an ok performance!

Parse Saving Objects from For In loop Issue

I'm having an issue saving my objects to the Parse object I created. I have Strings in a UITableview that the user uses a UISwitchto indicate they want to save the String. Once everything the user has selected what they want they push the save button on the UIToolbar just below the table. Once the save button is activated two checks are made: The first is to ensure the UISwitchhas been turned on the next is to check the contents of the NSDictionary that contains the strings. I have put in checks to ensure the for in loop is reading the values from the dictionary properly and it is. But the the only object being saved is the last object being ran through the for in loop. Not sure why. Here is my code:
#IBAction func questionSaved(sender: AnyObject){
for (key,value) in savedItems{
if key == cellNumber && value == true{
for (key,value) in physicalQuestions{
questionStore["physicalQuestion"] = value
questionStore.saveInBackgroundWithBlock{ (success, error) -> Void in
//Set Deleted Value to False when Succeeded
if (success){
self.questionStore["deleted"] = false
self.questionStore.saveInBackground()
}else{
self.questionStore["deleted"] = true
self.questionStore.saveInBackground()
}
}
}
}
}
}
Thanks for the help I really appreciate it.

How to check for an undefined or null variable in Swift?

Here's my code:
var goBack: String!
if (goBack == "yes")
{
firstName.text = passFirstName1
lastName.text = passLastName1
}
All I want to do is execute the if-statement if 'goBack' is undefined. How can I do that? (I don't know what to put in the blank)
The overall program is more complicated which is why I need the variable to be undefined at first. In short, I'm declaring 'goBack', asking the user to type in their first and last name, then continuing to the next view controller. That view controller has a back button that brings us back to the first view controller (where I declared 'goBack'). When the back button is pressed, a 'goBack' string is also passed of "yes". I also passed the first and last name to the next view controller but now I want to pass it back. I'm able to pass it back, its just a matter of making the text appear.
EDIT: firstName and lastName are labels while passFirstName1 and passLastName1 are variables from the second view controller.
"All I want to do is execute the if-statement if 'goBack' is undefined. How can I do that?"
To check whether a variable equals nil you can use a pretty cool feature of Swift called an if-let statement:
if let goBackConst = goBack {
firstName.text = passFirstName1
lastName.text = passLastName1
}
It's essentially the logical equivalent of "Can we store goBack as a non-optional constant, i.e. can we "let" a constant = goBack? If so, perform the following action."
It's really interesting, you can define a variable as optional, which means it may or may not be defined, consider the following scenerio:
you want to find out if the app has been installed before...
let defaults = NSUserDefaults()
let testInstalled : String? = defaults.stringForKey("hasApplicationLaunchedBefore")
if defined(testInstalled) {
NSLog("app installed already")
NSLog("testAlreadyInstalled: \(testInstalled)")
defaults.removeObjectForKey("hasApplicationLaunchedBefore")
} else {
NSLog("no app")
defaults.setValue("true", forKey: "hasApplicationLaunchedBefore")
}
Then all you need to do is write a function to test for nil...
func defined(str : String?) -> Bool {
return str != nil
}
And you've got it. A simpler example might be the following:
if let test : String? = defaults.stringForKey("key") != nil {
// test is defined
} else {
// test is undefined
}
The exclamation mark at the end is to for unwrapping the optional, not to define the variable as optional or not
"All I want to do is execute the if-statement if 'goBack' is undefined"
The guard statement (new in Swift 2) allows exactly this. If goBack is nil then the else block runs and exits the method. If goBack is not nil then localGoBack is available to use following the guard statement.
var goBack:String?
func methodUsingGuard() {
guard let localGoBack = goBack else {
print("goBack is nil")
return
}
print("goBack has a value of \(localGoBack)")
}
methodUsingGuard()
From The Swift Programming Language (Swift 3.1):
Constants and variables created with optional binding in an if
statement are available only within the body of the if statement. In
contrast, the constants and variables created with a guard statement
are available in the lines of code that follow the guard statement, as
described in Early Exit.

Resources