Populate SwiftyPickerPopover choices with array - ios

I'm using SwiftyPickerPopover to display popup control in order for user to select a value. Previously at another place in the app I've implemented it as:
let displayStringFor:((String?)->String?)? = { string in
if let s = string {
switch(s){
case "Lhe”:
return "Lh”
case "Khi":
return "Khi”
case "Isb”:
return "Isb"
case "Guj":
return "Guj"
default:
return s
}
}
return nil
}
let p = StringPickerPopover(title: "Select City", choices: ["Lhe”,”Khi”,”Isb”,”Guj”])
.setDisplayStringFor(displayStringFor)
.setDoneButton(
action: { popover, selectedRow, selectedString in
self.cityButton.setTitle(selectedString,for: .normal)
if selectedRow == 0 {
self.cityImage.image = UIImage(named: "Lhe")
} else if selectedRow == 1 {
self.cityImage.image = UIImage(named: "Khi")
} else if selectedRow == 2 {
self.cityImage.image = UIImage(named: "Isb")
} else if selectedRow == 3 {
self.cityImage.image = UIImage(named: "Guj")
}
})
.setCancelButton(action: {_, _, _ in
})
p.appear(originView: sender as! UIView, baseViewController: self)
Which you can see the values are hardcoded here. Now I've an API that gives me all the cities. So I made var mainCitiesArray = [City]() and called the API and parsed data to it.
In mainCitiesArray I've all the cities now which I want to display in this popup. How can I do that?

Do you need just to parse the cities names to string array?
E.G.
let citiesNamesArray = mainCitiesArray.map({
(city: City) -> String in
return city.name
})
And you can pass citiesNamesArray to choises parameter.

Related

Switch case on Swift generics not able to identify the object type [duplicate]

This question already has answers here:
Swift: Test class type in switch statement
(4 answers)
Closed 1 year ago.
I am showing a tableview with the list generated using the following code. The UI is properly populated. However on did select method, the call is not matched inside the generic case.
#objc public protocol ListProtocol {}
class EmptyListProtocol:ListProtocol {
let message:String
init(message:String){
self.message = message
}
}
class ListItemStrategy<T>: ListProtocol{
let object:T
init(listitem:T) {
self.object = listitem
}
}
struct CartInfo {
let card_id : String
let productname: String
let purchasedate:String
}
struct ProductOnSale {
let product_id:String
let product_name:String
let store_id:String
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = self.tablelist[indexPath.row]
switch item {
case is ListItemStrategy<Any>:
print("didtap")
case is EmptyListProtocol: break
default:
break
}
}
var table list : [ListProtocol] = []
I am not able to detect in particular, if a cart is selected or new purchase product is selected.
When checking the type you need to understand that SomeGeneric<Any> is a specific type and that you can't use the protocol Any here as something that represents any implementation of the generic type.
This means your switch would need to be something like
switch item {
case is ListItemStrategy<ProductOnSale>:
...
case is ListItemStrategy<CartInfo>:
...
case ...
}
I assume you want to access the specific objects which mean you need to type case item so it might be preferable to skip the switch and use if let to cast directly
let item = self.tablelist[indexPath.row]
if let cartInfo = item as? ListItemStrategy<CartInfo> {
...
} else if let productOnSale = item as? ListItemStrategy<ProductOnSale> {
...
} else if let ... {
...
}
Here is a simple example
let tableList : [ListProtocol] = [
ListItemStrategy(listitem: ProductOnSale(product_id: "product1", product_name: "", store_id: "")),
ListItemStrategy(listitem: CartInfo(card_id: "card1", productname: "", purchasedate: "")),
ListItemStrategy(listitem: ProductOnSale(product_id: "product2", product_name: "", store_id: "")),
ListItemStrategy(listitem: EmptyListProtocol(message: "Empty"))
]
for item in tableList {
if let cartInfo = item as? ListItemStrategy<CartInfo> {
print(cartInfo.object.card_id)
} else if let productOnSale = item as? ListItemStrategy<ProductOnSale> {
print(productOnSale.object.product_id)
} else if let emptyMessage = item as? ListItemStrategy<EmptyListProtocol> {
print(emptyMessage.object.message)
}
}

RSSelectionMenu multiple selection is not working after adding model class in it Swift

I am using RSSelectionMenu for adding multi-select in my application. It works fine when I add string array. But when I add my model class in it, multiple select feature stops working. It selects all elements when I click on it.
My code:
var filterApiArray: [Model]? = [Model]()
var simpleSelectedArray = [Model]()
func multipleFilterSelection() {
let selectionMenu = RSSelectionMenu(selectionStyle: .multiple, dataSource: filterApiArray ?? []) { (cell, name, indexPath) in
cell.textLabel?.text = name.county
}
selectionMenu.uniquePropertyName = "Model"
selectionMenu.cellSelectionStyle = .checkbox
selectionMenu.show(style: .alert(title: nil, action: AppStrings.done, height: nil), from: self)
selectionMenu.setSelectedItems(items: simpleSelectedArray) { (name, index, selected, selectedItems) in
print(name?.id, index, selected, selectedItems)
}
But if I add a static string array in RSSelectionMenu it works fine. Please comment if anyone works any work around.
This works for me and I am using RS like below:
let data = self.arrAgents.map{$0.agentName ?? ""} as [String]
let menu = RSSelectionMenu(selectionStyle: .multiple, dataSource: data) { (cell, name, indexPath) in
cell.textLabel?.text = name
}
menu.setNavigationBar(title: Strings.AGENT.text)
let selectedData = self.selectedAgent.map{$0.agentName ?? ""} as [String]
menu.setSelectedItems(items: selectedData) { (name, index, selected, selectedItems) in
// self.selectedTags = selectedItems
}
menu.show(from: self)
menu.onRightBarButtonTapped = { selectedItems in
menu.dismiss()
self.selectedAgent.removeAll()
for item in self.arrAgents{
for selectedItem in selectedItems{
if item.agentName == selectedItem{
self.selectedAgent.append(item)
}
}
}
self.collectionView.reloadData()
}
In above code, data is filled with an array named arrAgent and arrAgent has data from API.

QueryEndingAtValue function does not work correctly in Swift firebase

I have been implementing code that is to enable paging scroll to fetch data by a certain amount of data from firebase database.
Firstly, then error says
Terminating app due to uncaught exception 'InvalidQueryParameter',
reason: 'Can't use queryEndingAtValue: with other types than string in
combination with queryOrderedByKey'
The below here is the actual code that produced the above error
static func fetchPostsWith(last_key: String?, completion: #escaping (([PostModel]?) -> Void)) {
var posts = [PostModel]()
let count = 2
let ref = Database.database().reference().child(PATH.all_posts)
let this_key = UInt(count + 1)
let that_key = UInt(count)
let this = ref.queryOrderedByKey().queryEnding(atValue: last_key).queryLimited(toLast: this_key)
let that = ref.queryOrderedByKey().queryLimited(toLast: that_key)
let query = (last_key != nil) ? this : that
query.observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
for snap in snapshot.children {
if let d = snap as? DataSnapshot {
let post = PostModel(snapshot: d)
print(post.key ?? "")
posts.append(post)
}
}
posts.sort { $0.date! > $1.date! }
posts = Array(posts.dropFirst())
completion(posts)
} else {
completion(nil)
}
}
}
What it tries to do is to fetch a path where all posts are stored by auto id. But the error keeps coming out so I do not know what is wrong. Do you have any idea?
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// index is the last and last fetched then
print(self.feeds.count - 1 == indexPath.row, "index ", self.hasFetchedLastPage, "has fetched last")
if self.feeds.count - 1 == indexPath.row {
let lastKey = self.feeds[indexPath.row].key
if lastKey != self.previousKey {
self.getFeeds(lastKey: lastKey)
} else {
self.previousKey = lastKey ?? ""
}
}
print("Cell Display Number", indexPath.row)
}
func getFeeds(lastKey: String?) {
print(self.isFetching, "is fetching")
guard !self.isFetching else {
self.previousKey = ""
return
}
self.isFetching = true
print(self.isFetching, "is fetching")
FirebaseModel.fetchPostsWith(last_key: lastKey) { ( d ) in
self.isFetching = false
if let data = d {
if self.feeds.isEmpty { //It'd be, when it's the first time.
self.feeds.append(contentsOf: data)
self.tableView.reloadData()
print("Initial Load", lastKey ?? "no key")
} else {
self.hasFetchedLastPage = self.feeds.count < 2
self.feeds.append(contentsOf: data)
self.tableView.reloadData()
print("Reloaded", lastKey ?? "no key")
}
}
}
}
I want to implement a paging enabled tableView. If you can help this code to be working, it is very appreciated.
You're converting your last_key to a number, while keys are always strings. The error message tells you that the two types are not compatible. This means you must convert your number back to a string in your code, before passing it to the query:
let this = ref.queryOrderedByKey().queryEnding(atValue: last_key).queryLimited(toLast: String(this_key))
let that = ref.queryOrderedByKey().queryLimited(toLast: String(that_key))

If/Else based on existence of object in array?

I want to trigger an else statement if there is no object at the indexPath i look for in an array.
The array is
let exerciseSets = unsortedExerciseSets.sorted { ($0.setPosition < $1.setPosition) }
Then i use let cellsSet = exerciseSets[indexPath.row]
There is a chance, when a user is adding a new cell, that it wont already have an exerciseSet at the indexPath to populate it (adding a new set to an existing array of sets) and so I need to run an else statement to set up a blank cell rather than attempting to populate it and crashing my app.
However if i use if let then i get this error:
Initializer for conditional binding must have Optional type, not
'UserExerciseSet'
Here is the whole function for context if needed:
func configure(_ cell: NewExerciseTableViewCell, at indexPath: IndexPath) {
if self.userExercise != nil {
print("RESTORING CELLS FOR THE EXISTING EXERCISE")
let unsortedExerciseSets = self.userExercise?.exercisesets?.allObjects as! [UserExerciseSet]
let exerciseSets = unsortedExerciseSets.sorted { ($0.setPosition < $1.setPosition) }
if let cellsSet = exerciseSets[indexPath.row] { // this causes a creash when user adds new set to existing exercise as it cant populate, needs an else statement to add fresh cell
cell.setNumber.text = String(cellsSet.setPosition)
cell.repsPicker.selectRow(Int(cellsSet.setReps), inComponent: 0, animated: true)
let localeIdentifier = Locale(identifier: UserDefaults.standard.object(forKey: "locale") as! String)
let setWeight = cellsSet.setWeight as! Measurement<UnitMass>
let formatter = MassFormatter()
formatter.numberFormatter.locale = localeIdentifier
formatter.numberFormatter.maximumFractionDigits = 2
if localeIdentifier.usesMetricSystem {
let kgWeight = setWeight.converted(to: .kilograms)
let finalKgWeight = formatter.string(fromValue: kgWeight.value, unit: .kilogram)
let NumericKgResult = finalKgWeight.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789.").inverted)
cell.userExerciseWeight.text = NumericKgResult
} else {
let lbsWeight = setWeight.converted(to: .pounds)
let finalLbWeight = formatter.string(fromValue: lbsWeight.value, unit: .pound)
let NumericLbResult = finalLbWeight.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789.").inverted)
cell.userExerciseWeight.text = NumericLbResult
}
} else {
cell.setNumber.text = String((indexPath.row) + 1)
}
You could do something crazy like this:
if let cellSet = (indexPath.row < exerciseSets.count ? exerciseSets[indexPath.row] : nil) {
//
}
but it would be more straightforward to do:
if indexPath.row < exerciseSets.count {
let cellSet = exerciseSets[indexPath.row]
...
}
OK, so your problem is that you are trying to access a value in an array that may or may not be there.
If you just try to access the value based on indexPath you can crash because indexPath may refer to a value not there. On the other hand, the array does not return an optional so you can't use if let either.
I kind of like the idea of using an optional, so how about introducing a function that could return an optional to you if it was there.
Something like:
func excerciseSet(for indexPath: IndexPath, in collection: [UserExcerciseSet]) -> UserExcerciseSet? {
guard collection.count > indexPath.row else {
return nil
}
return collection[indexPath.row]
}
and then you can say:
if let cellsSet = exerciseSet[for: indexPath, in: excerciseSets] {
//It was there...showtime :)
} else {
cell.setNumber.text = String((indexPath.row) + 1)
}
Hope that helps you.
Just check the index against your array count:
if indexPath.item < exerciseSets.count {
// Cell exists
let cellsSet = exerciseSets[indexPath.row]
} else {
// cell doesn't exists. populate new one
}

How to perform DynamoDB scan using filterExpression in swift

I have a table that stores information about groups (GroupID, Members, Creator, LastAccessed, GroupName, etc..) as separate rows. Each group has a unique identifier (GroupID) as their hash (primary key). They also have an attribute called GroupName. I have a search box where the user inputs a partial group name. I want to perform a scan on the table and return all of the groups that begin with the users input. Here is what I have so far..
func searchForGroupsWithName(groupName: String) {
self.queryInProgress = true
let cond = AWSDynamoDBCondition()
let v1 = AWSDynamoDBAttributeValue();
v1.S = groupName
cond.comparisonOperator = AWSDynamoDBComparisonOperator.BeginsWith
cond.attributeValueList = [ v1 ]
let exp = AWSDynamoDBScanExpression()
//I only want to return the GroupName and GroupID.
//I think this should be ["GroupID", "GroupName"], but it requires a string
exp.projectionExpression = ??????????
//I am not sure how to incorporate cond with this.
exp.filterExpression = ??????????
dynamoDBObjectMapper.scan(GroupTableRow.self, expression: exp).continueWithBlock({ (task:AWSTask!) -> AnyObject! in
if task.result != nil {
let paginatedOutput = task.result as! AWSDynamoDBPaginatedOutput
for item in paginatedOutput.items as! [GroupTableRow] {
self.searchBarResults.append(item)
}
if ((task.error) != nil) {
print("Error: \(task.error)")
}
self.queryInProgress = false
return nil
}
self.queryInProgress = false
return nil
})
}
After getting help from WestonE, I fixed up my code and have a working example. I have pasted it below for anyone else who needs something similar
func searchForGroupsWithName(groupName: String) {
self.queryInProgress = true
let lowercaseGroupName = groupName.lowercaseString
let scanExpression = AWSDynamoDBScanExpression()
//Specify we want to only return groups whose name contains our search
scanExpression.filterExpression = "contains(LowercaseName, :LowercaseGroupName)"
//Create a scan expression to state what attributes we want to return
scanExpression.projectionExpression = "GroupID, GroupName, LowercaseName"
//Define our variable in the filter expression as our lowercased user input
scanExpression.expressionAttributeValues = [":LowercaseGroupName" : lowercaseGroupName]
dynamoDBObjectMapper.scan(GroupTableRow.self, expression: scanExpression).continueWithBlock({ (task:AWSTask!) -> AnyObject! in
if task.result != nil {
let paginatedOutput = task.result as! AWSDynamoDBPaginatedOutput
//use the results
for item in paginatedOutput.items as! [GroupTableRow] {
}
if ((task.error) != nil) {
print("Error: \(task.error)")
}
self.queryInProgress = false
return nil
}
self.queryInProgress = false
return nil
})
}
The projectionExpression should be a single comma delimited string ["GroupID", "GroupName"] => "GroupID, GroupName"
The filterExpression is also a string, and it's documentation is http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html#FilteringResults . In your case I think the expression would be "begins_with(groupName, BeginningCharacters)" But you might need to experiment with this.

Resources