Sqlite.swift live search - ios

I want to do a live search on the DB.
Lets say I want to search by companies and I have the following info on a column named companies.
Facebook
FastCompany
Facebook
Google
Microsoft
I have a textfield that has calls a function on editchanged.
#IBAction func searching(sender: AnyObject) {
tempstring = "%"+searchBar.text+"%"
println(tempstring)
user = user.select(name)
.filter(like(tempstring, name))
.limit(30, offset: 0)
collectionView?.reloadData()
}
It kind of works, if I start typing "fa"
It will show (Facebook, Facebook and FastCompany)
If I continue typing "fac" it will show (Facebook, Facebook)
But when I delete the last character "c" from the searchbox (leaving it in "fa" again) then the query displays nothing.
Any ideas on how I can solve this.

I think your issue is coming from over writing the user object with each search. This is fine as long as you only move forward, but when you go backwards like you did, the query messes up.
Instead, try adding a currentQuery property to your view controller with the collection view in it and set it to your user.select statement.
currentQuery = user.select(name)
.filter(like(tempstring, name))
.limit(30, offset: 0)
Then use the currentQuery object to display the results instead. This way, no matter what you're searching, it will match everything.

Related

How do I add url links to WinWheel.js image slices?

How do I add a clickable link to any of the image slices so the user can click a slice to go to a url? Currently they can spin the wheel to get a random url choice. I got the random spin part working using “IF” but I got stuck figuring out the image slice click idea.
I'm currently working at the top of page in an area called:
//add code here?
if (clickedSegment) {
clickedSegment.indicatedSegment = "Jane";
window.location = "https://www.mikeloucas.com";
theWheel.draw();
and I have one url working due to the "clickedSegment" but it's active on all the slices and not what I need (I'm try to target Jane) so I'll keep at it unless some has an answer.
Separately but related:
This is one of the image segments I'm trying to add add link to:
{'image' : 'https://mikeloucas.com/wheel/jane.png', 'text' : 'Jane'},
The original tutorial shows how to change the colour of a slice on click, but I’d like to go to a url instead.
Original Click Code: http://dougtesting.net/winwheel/docs/tut15_get_segment_clicked
The thing Im working on: https://codepen.io/mikeloucas/pen/qBarWoP
In my Codepen example I turned off the “IF” load url because its just in the way when testing, but feel free to turn it on see it in action. It’s WAY down the page lol; you’ll see:
if (indicatedSegment.text == "Jane") {
window.location = "https://www.mikeloucas.com";
WAS:
let clickedSegment= theWheel.getSegmentAt(e.clientX, e.clientY);
if (clickedSegment) {
clickedSegment.indicatedSegment = "Jane";
window.location = "https://www.mikeloucas.com";
theWheel.draw();
IS NOW:
let segmentImage = theWheel.getSegmentAt(e.clientX, e.clientY);
if (segmentImage.text == "Jane") {
window.location = "https://www.mikeloucas.com";}
else if (segmentImage.text == "othername")...{
}
...theWheel.draw();
The trick (for me) was changing "clickedSegment" to "segmentImage" because "segmentImage" was already calling the array of names. I also add an "ELSE IF" because I had lots of slices to work work with.
See working thingy here: https://codepen.io/mikeloucas/pen/qBarWoP
My version needs to have the URL and NAME LIST entered twice; it would be cool if someone knows how to do it once (one set instead of two) but maintain both functions of the "CLICK url" and "SPIN url", so I'm still open to advice.
I'd prefer one set over two. :-)

Swift Firebase database overwriting

I am making a real-time messenger using Firebase. Currently, whenever I press a button I want a new message to be appended to the channel with the index of the message, but currently, whenever I press the button a new message is created that overwrites the old message. I know that setValue is usually the issue, but I really cannot tell what I'm doing wrong. What the database looks like before I add my new message. This is what it looks like after I add a new message here, and then the code I am using to add to the database.
#IBAction func sendMessageTapped(_ sender: Any) {
if messageTextField.text == "" {
print("blank")
return
} else {
// First we will update the amount of messages that the channel has.
ref.child("channels").child(channelName!).setValue(["numberOfMessages" : numberOfMessages+1 ])
numberOfMessages += 1
// after we have updated the amount of messages we will try to create a new message.
ref.child("channels").child(channelName!).child("messages").child(String(numberOfMessages)).child("message").child("content").setValue(messageTextField.text)
ref.child("channels").child(channelName!).child("messages").child(String(numberOfMessages)).child("message").child("name").setValue("Buddy")
}
}
ok, Firebase is not a traditional table based database, is a DOCUMENT based database. At the very top you have a thing called a "collection" which is just a list of "document" things. In your case, you'd have several collection things to serve as channels: "General", "TopicQ", "InterstingStuff" etc, and within them each message as a document. No need to have a document, to then list the messages within it.
Second, you don't need indexes as you're using them, make the message id an attribute of the message, because firebase support querying by field, and even then is questionable because if you make each message a document, they will have their own auto generated id's if you want.
Third, in your code you're rewriting the whole document each time, this is why you lose your previous messages, so if you keep it, you need to add a merge option:
// Update one field, creating the document if it does not exist.
db.collection("cities").document("BJ").setData([ "capital": true ], merge: true)
you probably want to do something like this. This is what I did for my app, hope this helps someone. This rootRef.childByAutoId() generates a new entry with unique id. You can use this as reference for your case.
let rootRef = Database.database().reference(withPath: "channels")
let childRef = rootRef.childByAutoId()
let values = ["Type": self.textField.text!, "message": self.textView.text!] as? [String : Any]
childRef.updateChildValues(values)

MapKit Define the desired type of search results (Country, city, region, etc)

For an app i'm building, I want to implement a feature that allows users to specify the geographical origin of wines (country (e.g. France), region (e.g. Bordeaux), subregion (e.g. Paullac)).
I want to make sure that I don't have to add all available countries myself, and that all information that comes into the database is valid. Therefore, I decided to do it as follows:
User adds a new wine and types the name of the country it comes from
While typing, the app searches in the apple maps database
The results from this search get displayed as suggestions, and when the user taps a suggestion, the app creates a Country object with all relevant information. The wine van only be saved when such an object is present
This works fine, except one thing: Apple maps returns anything, like restaurants, shops, etcetera, from anywhere.
My question: How can I specify WHAT I am looking for? I can only specify the region I'm searching in, which is irrelevant in my case. I would like to be able to tell apple maps to ONLY look for countries, regions, cities, whatever. Is this possible in a way? I have exhausted google for this and found no way thus far.
Going off what #Trevor said, I found rejecting results where either the title or subtitle have numbers yields pretty good results if you only want cities and towns.
Swift 4.1 code:
// Store this as a property if you're searching a lot.
let digitsCharacterSet = NSCharacterSet.decimalDigits
let filteredResults = completer.results.filter { result in
if result.title.rangeOfCharacter(from: digitsCharacterSet) != nil {
return false
}
if result.subtitle.rangeOfCharacter(from: digitsCharacterSet) != nil {
return false
}
return true
}
or more compactly:
let filteredResults = completer.results.filter({ $0.title.rangeOfCharacter(from: digitsCharacterSet) == nil && $0.subtitle.rangeOfCharacter(from: digitsCharacterSet) == nil })
The best solution we found was to filter our results using a comma in the result's title. This mostly returned only results that matched a city's format, e.g Detroit, MI, United States. We added this filter to the ones suggested by #Ben Stahl. Ben's solution filtered out edge cases where a comma formed part of the business' name.
This usually returns the correct result within three characters. To answer the OP's question, you could then parse this string by city, state or country to get the desired result.
For better results you could use the Google Places API.
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
self.searchResults = completer.results.filter { result in
if !result.title.contains(",") {
return false
}
if result.title.rangeOfCharacter(from: CharacterSet.decimalDigits) != nil {
return false
}
if result.subtitle.rangeOfCharacter(from: CharacterSet.decimalDigits) != nil {
return false
}
return true
}
self.searchResultsCollectionView.reloadData()
}
I have worked with MapKit and don't believe you can do autocomplete assistance on user entries as they type the best solution I found is Google Place API autocomplete
iOS right now provides receiving geo-coordinates when sending a well-formatted address , or you can receive an address when sending a pair of coordinates. Or points of interest for locations names or coordinates.
There was a class added to MapKit in iOS 9.3 called MKLocalSearchCompleter which helps with autocompletion. You can filter what is returned by using 'MKSearchCompletionFilterType' but that isn't the most extensive and might not fully help with your situation. It does return cities and countries as results but it also returns businesses when I've used it.
One possible option is to filter the returned results again on the app side and exclude all results that have a numeric character in them.
func setupCompleter() {
self.searchCompleter = MKLocalSearchCompleter()
self.searchCompleter?.delegate = self
self.searchCompleter?.filterType = .locationsOnly
self.searchCompleter?.queryFragment = "Bordeaux"
}
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) {
print("Results \(completer.results)")
// Do additional filtering on results here
}
In addition to Allan's answer, I've found that if you filter by the subtitle property of a MkLocalSearchCompletion object, you can remove the business entries.

how do you iterate through elements in UI testing in Swift/iOS?

I understand how I could, for example look at one element in a table, but what's the correct way to simply iterate through all the elements, however many are found, to for example tap on them?
let indexFromTheQuery = tables.staticTexts.elementBoundByIndex(2)
Okay I succeeded in figuring out the simple syntax. I have just started working with Swift and so it took me sleeping on it to think of the answer.
This code works:
var elementLabels = [String]()
for i in 0..<tables.staticTexts.count {
elementLabels.append (tables.staticTexts.elementBoundByIndex(i).label)
}
print (elementLabels)
My guess is the answer is there is no way to do so.
The reason is because of my experience with one of the critical iOS components while making a number of UI tests: UIDatePicker.
If you record a test where you get the page up and then you spin the picker, you will notice that the commands are all non-specific and are screen related. In the case of the picker, however, community requests resulted in the addition of a method for doing tests: How to select a picker view item in an iOS UI test in Xcode?.
Maybe you can add a helper method to whatever controller contains this table. Also, keep in mind that you can easily add methods without polluting the class interface by defining them as extensions that are in test scope only.
For Xcode 11.2.1, SwiftUI and swift 5 based app, the following code works for testing a list, each element in this case appears as a button in the test code. The table is set up like this (for each row) :
NavigationLink(destination: TopicDetail(name: "Topic name", longDesc: "A description")) {
TopicRow(thisTopic: top).accessibility(identifier: "newTopicRow_\(top.name!)")
}
Then I catch the members of the table by getting the buttons into an array:
let myTable = app.tables.matching(identifier: "newTopicTable")
var elementLabels = [String]()
for i in 0..<myTable.buttons.count {
elementLabels.append (tablesQuery.buttons.element(boundBy: i).label)
}
print (elementLabels)
Finally, I deleted each member of the table by selecting the detail view where I have a delete button, again with
.accessibility(identifier: "deleteTopic"
I wanted to delete all members of the table:
for topicLabel in elementLabels {
let myButton = app.buttons[topicLabel]
myButton.firstMatch.tap()
app.buttons["deleteTopic"].tap()
}

How to implement pagination when using amazon Dynamo DB in rails

I want to use amazon Dynamo DB with rails.But I have not found a way to implement pagination.
I will use AWS::Record::HashModel as ORM.
This ORM supports limits like this:
People.limit(10).each {|person| ... }
But I could not figured out how to implement following MySql query in Dynamo DB.
SELECT *
FROM `People`
LIMIT 1 , 30
You issue queries using LIMIT. If the subset returned does not contain the full table, a LastEvaluatedKey value is returned. You use this value as the ExclusiveStartKey in the next query. And so on...
From the DynamoDB Developer Guide.
You can provide 'page-size' in you query to set the result set size.
The response of DynamoDB contains 'LastEvaluatedKey' which will indicate the last key as per the page size. If response does't contain 'LastEvaluatedKey' it means there are no results left to fetch.
Use the 'LastEvaluatedKey' as 'ExclusiveStartKey' while fetching next time.
I hope this helps.
DynamoDB Pagination
Here's a simple copy-paste-run proof of concept (Node.js) for stateless forward/reverse navigation with dynamodb. In summary; each response includes the navigation history, allowing user to explicitly and consistently request either the next or previous page (while next/prev params exist):
GET /accounts -> first page
GET /accounts?next=A3r0ijKJ8 -> next page
GET /accounts?prev=R4tY69kUI -> previous page
Considerations:
If your ids are large and/or users might do a lot of navigation, then the potential size of the next/prev params might become too large.
Yes you do have to store the entire reverse path - if you only store the previous page marker (per some other answers) you will only be able to go back one page.
It won't handle changing pageSize midway, consider baking pageSize into the next/prev value.
base64 encode the next/prev values, and you could also encrypt.
Scans are inefficient, while this suited my current requirement it won't suit all!
// demo.js
const mockTable = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
const getPagedItems = (pageSize = 5, cursor = {}) => {
// Parse cursor
const keys = cursor.next || cursor.prev || [] // fwd first
let key = keys[keys.length-1] || null // eg ddb's PK
// Mock query (mimic dynamodb response)
const Items = mockTable.slice(parseInt(key) || 0, pageSize+key)
const LastEvaluatedKey = Items[Items.length-1] < mockTable.length
? Items[Items.length-1] : null
// Build response
const res = {items:Items}
if (keys.length > 0) // add reverse nav keys (if any)
res.prev = keys.slice(0, keys.length-1)
if (LastEvaluatedKey) // add forward nav keys (if any)
res.next = [...keys, LastEvaluatedKey]
return res
}
// Run test ------------------------------------
const runTest = () => {
const PAGE_SIZE = 6
let x = {}, i = 0
// Page to end
while (i == 0 || x.next) {
x = getPagedItems(PAGE_SIZE, {next:x.next})
console.log(`Page ${++i}: `, x.items)
}
// Page back to start
while (x.prev) {
x = getPagedItems(PAGE_SIZE, {prev:x.prev})
console.log(`Page ${--i}: `, x.items)
}
}
runTest()
I faced a similar problem.
The generic pagination approach is, use "start index" or "start page" and the "page length". 
The "ExclusiveStartKey" and "LastEvaluatedKey" based approach is very DynamoDB specific.
I feel this DynamoDB specific implementation of pagination should be hidden from the API client/UI.
Also in case, the application is serverless, using service like Lambda, it will be not be possible to maintain the state on the server. The other side is the client implementation will become very complex.
I came with a different approach, which I think is generic ( and not specific to DynamoDB)
When the API client specifies the start index, fetch all the keys from
the table and store it into an array.
Find out the key for the start index from the array, which is
specified by the client.
Make use of the ExclusiveStartKey and fetch the number of records, as
specified in the page length.
If the start index parameter is not present, the above steps are not
needed, we don't need to specify the ExclusiveStartKey in the scan
operation.
This solution has some drawbacks -
We will need to fetch all the keys when the user needs pagination with
start index.
We will need additional memory to store the Ids and the indexes.
Additional database scan operations ( one or multiple to fetch the
keys )
But I feel this will be very easy approach for the clients, which are using our APIs. The backward scan will work seamlessly. If the user wants to see "nth" page, this will be possible.
In fact I faced the same problem and I noticed that LastEvaluatedKey and ExclusiveStartKey are not working well especially when using Scan So I solved Like this.
GET/?page_no=1&page_size=10 =====> first page
response will contain count of records and first 10 records
retry and increase number of page until all record come.
Code is below
PS: I am using python
first_index = ((page_no-1)*page_size)
second_index = (page_no*page_size)
if (second_index > len(response['Items'])):
second_index = len(response['Items'])
return {
'statusCode': 200,
'count': response['Count'],
'response': response['Items'][first_index:second_index]
}

Resources