Customizing QuickType Suggestions in iOS - ios

Well, I suspect that this is a forlorn question, ultimately doomed to sadden and disappoint me, but I want to have the question on the record for the future.
I will add this to the existing questions: here and here
The scenario is that I am creating an administration app that is designed to allow folks to edit the values in a database on a server. I have a fairly zippy API that can be used to allow REST-style data exchange.
I have the ability to download a list of values that the user can apply as search terms when they log in, and I'd like to be able to help them to enter things quickly.
For example, town names. If they enter "B", then I'd like to be able to offer "Bayshore", "Babylon" and "Bohemia" as suggestions, and so on.
A completely legit application.
I am under the assumption that there currently does not exist a QuickType API.
Am I wrong?

There is no QuickType API but you can get similar functionality with the ACEAutocompleteBar library from GitHub. Just for the record, I have listed the steps to get this working with Swift:
1) Import the files from the folder "ACEAutocompleteBar" to your project.
2) Create a bridging header and write #import "ACEAutocompleteBar.h" at the top.
3) Make your view containing the text field an ACEAutocompleteDataSource and ACEAutocompleteDelegate.
4) Implement the minimumCharactersToTrigger function
func minimumCharactersToTrigger(inputView: ACEAutocompleteInputView!) -> UInt {
return 1
}
5) Implement the inputView function.
func inputView(inputView: ACEAutocompleteInputView!, itemsFor query: String!, result resultBlock: (([AnyObject]!) -> Void)!) {
inputView.hidden = false
inputView.alpha = 0.75
if resultBlock != nil{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
var data:NSMutableArray = []
if(self.myTextField.isFirstResponder()){
//query your data source with 'query' and add any of the results to data to pass back
}
dispatch_async(dispatch_get_main_queue()) {resultBlock(data as [AnyObject])}
}
}
}
6) Implement the textField function.
func textField(textField: UITextField!, didSelectObject object: AnyObject!, inInputView inputView: ACEAutocompleteInputView!) {
textField.text = String(object)
}
7) Call setAutocompleteWithDataSource on the textField.
self.myTextField.setAutocompleteWithDataSource(self, delegate: self, customize: {
inputView in
// customize the view (optional)
inputView.font = UIFont.systemFontOfSize(20)
inputView.textColor = UIColor.whiteColor()
inputView.backgroundColor = UIColor.blueColor()
inputView.hidden = false
})
Hope that helps anyone looking for this kind of functionality!

Related

Is it possible to match UI elements by labels using regex in XCUIElementQuery?

I'm currently learning how to create UI Tests in XCode.
I use XCUIElementQuery to locate alert and close it
let dismissSavedPasswordButton = app.alerts["Select a Saved Password to Use With “My App”"].buttons["Not Now"]
But for older devices (e.g. running iOS 9), this code should look like this
let dismissSavedPasswordButton = app.alerts["Select a Saved Safari Password to Use With “My App”"].buttons["Not Now"]
Is it possible to rewrite this code to make it universal?
It is possible if you extend XCUIElementQuery class. I do something similar in my code:
extension XCUIElementQuery {
func softMatching(substring: String) -> [XCUIElement] {
return self.allElementsBoundByIndex.filter { $0.label.contains(substring) }
}
}
After that, you can match elements like this:
let dismissSavedPasswordButton = app.alerts.softMatching(substring: "Password").first!.buttons["Not Now"]
Not a regex, but should work:
app.alerts.element(boundBy: 0)
Since you should have only one alert on the screen, just query it by position.
Unless you have more than one not now button on the screen you could just use
app.buttons["Not Now"]
You don't need to specify any more than that.

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.

Using UIButton Text as Text Input - Swift

Hello I have a profilelbl variable as below which is a uibutton. I want the text of the button to be an input in my database (parse). But I couldn't figured it out. I tried lots of things but still getting error:
#IBOutlet weak var profileLbl: UIButton!
var notification = PFObject(className: "notifications")
notification["actionReceiverName"] = profilelbl.text /*not working*/
/* also tried
notification["actionReceiverName"] = sender.profilelbl.text
notification["actionReceiverName"] = profilelbl.title */
you can do it easy like that
if let button = profilelbl as? UIButton {
if let title = button.titleForState(.Normal) {
println(title)
notification["actionReceiverName"] = title
}
}
Using UI objects to save/load data is a very bad idea. Using user-visible strings programmatically is an even worse idea. #ÖzgürErsil answered the question you asked, but the better answer to your question is "Don't do that. Ever."
Here are 2 examples where your approach will fail:
If 6 months later you want to change your UI and rename your button,
you won't remember that the button title is used in code and your
code will break. To that you would have to alter your database to
use a different string value.
If you decide to localize your app for foreign
languages, the button titles will come up in the local language, and
your code will break. There is no clean way to fix this problem,
since each local language would use a different version of the
button title.
It would be better to put unique tag numbers on your buttons, then look up text strings using the tags and pass those strings to your database.
Say you have button tags starting at 100.
You'd use code like this:
let buttonStrings = ["button1", "button2", "button3"]
let baseButtonTag = 100;
#IBAction func handleButton(sender: UIButton)
{
let tag = sender.tag
if tag >= baseButtonTag && tag < baseButtonTag + buttonStrings.count
{
let index = sender.tag - baseButtonTag
let buttonString = buttonStrings[index];
//Now use buttonString with your database as desired.
}
}

Cant't take an object id from a parse object and use it in other sections of code

Ive spent countless hours trying to figure out why I can't seem to take the object id of an object that has been saved to parse and use it in other sections of my code. Yes I have searched and found topics about this on stack overflow and other websites and I have read the parse.com documentation but none of the answers will allow me to fix this issue. I am pretty new to coding and I am only familiar with swift at the moment. If you can help me figure this out I would more than appreciate your help.
I declare objectID as a string variable at the top of the code. Later when a send button is tapped I save the data to parse successfully and attempt to capture the datas objectId by passing the objectID variable to the saveToParse function as an inout. The project does not show any errors. The code builds and runs. NSLog(id) shows me the proper objectId that I want in the console. However it does not show anything when NSLog(self.objectID) is called outside of the function.
Like I said, I'm trying to use this objectId outside the function in other parts of the code, but it just doesn't want to save the objectId to my variable. I've tried many other methods and this is the closest I have got to making it work.
Im new at this so hope I've explained my situation clearly, if you need any more information to solve this don't hesitate to ask. Thanks for reading :)
var objectID:String = String() // declared at the top (underneath Class)
#IBAction func sendQuestionTapped(sender: UIButton) {
// Send question and answers to Parse
saveToParse(&objectID)
// Save Question object ID
NSLog(self.objectID) // Test if objectID is still Question.objectId
// Move to the results view controller with information
performSegueWithIdentifier("toResultsSegue", sender: self)
}
func saveToParse(inout id:String) {
var Question = PFObject(className:"Question")
Question["question"] = questionToPass
Question["answers"] = arrayToPass
Question.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
// The object has been saved.
dispatch_async(dispatch_get_main_queue()) {
id = Question.objectId!
NSLog(id)
}
}
else {
// There was a problem, check error.description
if error != nil {
NSLog(error!.localizedDescription)
}
}
}
}

How can an item be removed from UIPasteboard in iOS?

I see methods for adding items to a pasteboard. I also see that you can get notifications on pasteboard item removal. But I can't seem to find how to remove an item (both key and value) from a pasteboard. How can an item be removed from a pasteboard?
The only difference between the setValue("") and the removeObject() approach is the log provided by UIPasteboard.items. Functionally, either solution will clear your entry, and clear the associated dictionary as well.
pb.contains(pasteboardTypes: [UIPasteboardName.general.rawValue])
will returns false.
You can delete a single entry by manipulating the dictionary, not replacing it by "", unlike How to clear/empty pasteboard on viewWillDisappear.
Option 1
Use this if you want to clear all entries. pb.items will log [].
#IBAction func deleteEntirePasteboard(_ sender: AnyObject) {
UIPasteboard.remove(withName: UIPasteboardName(rawValue: pasteboardName()))
}
Option 2
Use this to remove an entry and its dictionary. pb.items will also log [] as all entries are cleared.
#IBAction func clearUsingRemove(_ sender: AnyObject) {
let pb = pasteBoard()
pb.items = [[String : Any]]()
}
Option 3
This is the generally accepted approach. pb.items will log [{}], showing an empty dictionary. While pb.containsPasteboardTypes will return nil as well, it is arguably not as thorough as the two other options above.
#IBAction func clearUsingQuotes(_ sender: AnyObject) {
let pb = pasteBoard()
pb.setValue("", forPasteboardType: UIPasteboardName.general.rawValue)
}
Use this support code to access the UIPasteboard in the 3 solutions listed above:
func pasteBoard() -> UIPasteboard {
return UIPasteboard(name: UIPasteboardName(rawValue: pasteboardName()),
create: true)!
}
func pasteboardName() -> String {
return "19172176"
}
► Find this solution on GitHub and additional details on Swift Recipes.
I had a similar issue. I needed to clean up several entries without touching other items:
let pasteboard = UIPasteboard.general
pasteboard.items = pasteboard.items.filter{ $0.keys.first != "type.to.be.removed" }
Now use whatever boolean expression in the filter closure to remove items.

Resources