This question already has answers here:
How to throttle search (based on typing speed) in iOS UISearchBar?
(12 answers)
Closed 5 years ago.
I'm coding a viewController which permit to search specifics users. I have a searchBar and a listener:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
doSearch()
}
func doSearch() {
if let searchText = searchBar.text?.lowercased() {
self.users.removeAll()
self.tableView.reloadData()
Api.User.queryUsers(withText: searchText, completion: { (user) in
self.users.append(user)
self.tableView.reloadData()
})
}
}
The queryUser func:
func queryUsers(withText text: String, completion: #escaping (Userm) -> Void) {
REF_USERS.queryOrdered(byChild: "username_lowercase").queryStarting(atValue: text).queryEnding(atValue: text+"\u{f8ff}").queryLimited(toLast: 5).observeSingleEvent(of: .value) { (snapshot) in
snapshot.children.forEach({ (s) in
let child = s as! DataSnapshot
if let dict = child.value as? [String: Any] {
let user = Userm.transformUser(dict: dict, key: child.key)
completion(user)
}
})
}
}
The problem is that, with Firebase, when I write too fast I can have the same user displayed several times. I would like to have a function which can do something like this:
// if user is not already contained in the array
-> Display it
I tried to use the .contains() function but I have not arrived at a good result, I'm still a beginner in Swift.
Do you have any idea please?
You can choose to filter the array the TableView uses to display data. This can be done either by casting it to a Set (since Sets only contain unique values) and then back to an Array again, or with an extension to the User class so it conforms to the Equatable protocol. Using the set option can be done like this:
let array = ["one", "one", "two", "two", "three", "three"]
let unique = Array(Set(array))
// ["one", "two", "three"]
Using the protocol would look something like this:
extension User: Equatable {
static func == (lhs: User, rhs: User) -> Bool {
//make sure the User class has something that can uniquely identify them.
return lhs.identifier == rhs.identifier
}
}
I think you can use filters to get a valid array.
something like this:
var someObject:SomeObject = receivedObjectFromMars
var arrayOfObjects:Array<SomeObject> = [object1, object2, object3, object4]
// imagine that 'someObject' has the same email of object1 and object 4.
var filteredArray:Array<String> = [];
filteredArray = arrayOfObjects.filter({ (obj) -> Bool in
//so when you filter the array, you will compare a specific
// attribute (e.g: email)
return obj.email != someObject.email
})
// current filteredArray = [object2, object3]
So, after that, if you want to add the specific element, you just add.
filteredArray.append(someObject);
You can use it with other types, like strings, int, whatever.
get help here, too: Swift 3 filter array of objects with elements of array
JLU.
Related
Goal of the code:
To assign a struct dictionary with Strings as Keys and String Arrays as values to a variable and then pull one (can be at random) specific String key value in the String Array and return that one String element in the underlying String Array so that it can be used elsewhere (potentially assigned to a label.text)
Essentially (please reference code below), I want to access one value at random in myDictionary using a specific key ("keyOne"), and pull, let's say, "Value2" then return only the string "Value2" from the underlying String Array associated with "keyOne" using indexing.
Errors are in the code below.
The issue I'm thinking is that I haven't figured out how to turn my final var Testing = dict["keyOne"] into an Int compatible index... if it was an index, the code would pull an Int value and the corresponding String from the three Strings in the underlying value array (due to the three String values associated with "keyOne").
Also, variableView() just inherits the datasource from several other containers, but the var dataSource : Structure? is the main reference, so that is what I included.
Code so far:
let myDictionary = [Structure(name: "keyOne", text: ["Value1", "Value2", "Value3"]), Structure(name: "keyTwo", text: ["Value4", "Value5", "Value6"])]
lazy var dict = Dictionary(uniqueKeysWithValues: myDictionary.lazy.map { ($0.name, $0.text) })
struct Structure: Hashable {
var name: String
var text: [String]
init(name: String, text: [String]){
self.name = name
self.text = text
}
}
func variable(at index: Int) -> variableView {
let variable = variableView()
var Testing = dict["keyOne"]
variable.dataSource = Testing![index] <- Cannot assign value of type 'String' to type 'structure'
return variable
var dataSource : Structure? {
didSet {
label.text = "This is a test"
} else {
// n/a
}
}
Please note that the error message is above in the code for variable.dataSource = Testing![index].
I am also suspecting that my issue lies in the "looping" logic of how I am assigning a variable with a struct, to a datasource which references that same struct.
Any help is appreciated as I have been stuck on this for legitimately a week (I truly have exhausted every single StackOverflow answer/question pair I could find).
THANK YOU!
EDIT:
I found this documentation to assist me greatly with this, and I recommend anyone with a similar question as mine to reference this: https://swift.org/blog/dictionary-and-set-improvements/
Given the question and the discussion in the comments I would add a mutating func to the struct that removes and returns a random string
mutating func pullText() -> String? {
guard let index = text.indices.randomElement() else {
return nil
}
return text.remove(at: index)
}
Example
if let index = myDictionary.firstIndex(where: { $0.name == "keyOne" }),
let text = myDictionary[index].pullText() {
someLabel.text = text
}
Here is another example based on the code in the question
Assuming VariableView looks something like this
struct VariableView: View {
var dataSource : Structure?
var word: String?
var body: some View {
Text(word ?? "")
}
}
Then the func variable can be changed to
func variable() -> VariableView {
var variable = VariableView()
if let index = dict.firstIndex(where: { $0.name == "keyOne" }) {
variable.dataSource = dict[index]
variable.word = dict[index].pullText()
}
return variable
}
This question already has answers here:
Swift: Better way to remove a specific Object from an array?
(5 answers)
Closed 2 years ago.
I have this custom array:
var customArray = [CustomItem]()
Custom item is:
class CustomItem {
let firstItem: Enumeration
let data: Any
// ...
}
Enumeration contains an enumaration with different case. For example case example1, case example2 etc.
I add element with append with all the info like firstItem (enumeration etc).
What I need is to check if in my customArray I have a given item in my enumeration. Let's say I have to check if in customArray the enumeration .example1 exist (because I append it before) and in case delete it.
What is the safest and most elegant way to perform this?
Extra: what If i would like to add one element at the end of this custom arrray?
if you want find a specific item index you can use firstIndex(of: )
like this:
let index = customArray.firstIndex(of: CustomItem)
customArray.remove(at: index)
EDIT:
You have to make your object Equatable with an extension:
enter codextension CustomItem: Equatable {
static func ==(lhs: CustomItem, rhs: CustomItem) -> Bool {
return lhs.firstItem == rhs.firstItem
} }
Now you can compare the objects with each other.
this is my code from playground:
enum Enumeration {
case opt1
case op2
}
struct CustomItem {
var firstItem: Enumeration
let data: Any
}
extension CustomItem: Equatable {
static func ==(lhs: CustomItem, rhs: CustomItem) -> Bool {
return lhs.firstItem == rhs.firstItem
}
}
class ViewController: UIViewController {
var customArray = [CustomItem]()
func searchAndDestory() {
let index = customArray.firstIndex(of: CustomItem.init(firstItem: .op2, data: 0)) ?? 0
customArray.remove(at: index)
}
}
Problem
I need to save a List in Realm, which is made up of a the properties of an Array of Struct Objects (which has been passed through a Segue and is popualating a tableview). This is in the form of an 'exercise name' and 'number of reps' on each row.
What have I tried?
I have matched the Realm Object with the Struct in terms of fields and format and attempted to save the array as a list e.g. "=List< array >" but this doesn't work ("use of undeclared type"). I've also tried various methods of trying to save the properties of each table row but again, couldn't get that to work (e.g. = cell.workoutname)
Research I found this How to save a struct to realm in swift? however, this isn't for saving arrays of objects I don't think. This did however (first answer), give me the idea of potentially saving the values contained within each row to Realm instead of the actual Struct array. I also found this Saving Array to Realm in Swift? but I think this is for when the array is already made up of Realm Objects, not Struct instances like in my case.
Code and details
Structs
I have a Struct as per below. Another struct, (Workout Generator) has a function which generates x number of instances of these objects. These are then passed via a Segue to a new VC TableView (each row displays a workout name and number of reps):
struct WorkoutExercise : Hashable, Equatable{
let name : String
let reps : Int
var hashValue: Int {
return name.hashValue
}
static func == (lhs: WorkoutExercise, rhs: WorkoutExercise) -> Bool {
return lhs.name == rhs.name
}
}
I then have the following Realm Objects. One is for saving a 'WorkoutSession'. This will contain a Realm List of WorkoutExercise Realm objects.
class WorkoutSessionObject: Object {
#objc dynamic var workoutID = UUID().uuidString
#objc dynamic var workoutName = ""
let exercises = List<WorkoutExerciseObject>()
var totalExerciseCount: Int {
return exercises.count
}
}
class WorkoutExerciseObject: Object {
#objc dynamic var name = ""
#objc dynamic var reps = 0
}
I have tried the following code when trying to save the Workout details to Realm :
func saveToRealm() {
let workoutData = WorkoutSessionObject()
workoutData.workoutName = "test"
workoutData.workoutID = UUID().uuidString
workoutData.exercises = List<selectedWorkoutExerciseArray>
}
What I think I need to do from reading the other answers
Option 1 - instead of trying to save the actual array, save the 'name' and 'reps' from each table row instead?
Option 2 - somehow convert the 'selectedWorkoutExerciseArray' into a list of realm objects?
of course there might be other options! Any help/ideas appreciated!
Why populate 2 separate lists if it needs to be persistent anyway? Just use the list in Realm to populate your table view. Here's a simple example of populating the list using append (just like any array):
class SomeClass: Object {
#objc dynamic var id: String = ""
var someList = List<SomeOtherClass>()
convenience init(id: String) {
self.init()
self.id = id
}
}
#objcMembers class SomeOtherClass: Object {
dynamic var someValue: String = ""
convenience init(value: String) {
self.init()
someValue = value
}
}
func addToList(someOtherClass: SomeOtherClass) {
let realm = try! Realm()
if let someClass = realm.objects(SomeClass.self).last {
do {
try realm.write({
someClass.someList.append(someOtherClass)
})
} catch {
print("something went wrong")
}
}
}
I have a very similar functionality, that allow the user to select from a table view. What I do is create a List from the selection like:
var arrayForSelectedObjects = [CustomObject]()
...
let aList = List<CustomObject>()
aList.append(objectsIn: arrayForSelectedObjects)
//I then assign the created list to the main object and save it.
let realmObject = MainObject()
realmObject.list = aList
My CustomObject is also stored on the realm db.
My MainObject is defined like so:
class MainObject : Object {
#objc dynamic var title: String?
var list = List<CustomObject>()
}
Below is my custom object class.
class UserGroups: NSObject {
let groupName: String
let users: [CheckIn]?
init(json:JSON) {
self.groupName = json[Constants.Models.UserGroups.groupName].stringValue
self.users = UserGroups.getUserGroupsList(jsonArray: json[Constants.Models.UserGroups.users].arrayValue)
}
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn]{
return jsonArray.flatMap({ (jsonItem: JSON) -> CheckIn in
return CheckIn(json: jsonItem)
})
}
}
I've an array of above custom objects. How can I combine 2 or more custom objects into a single object by merging users of every object having same groupName.
Below is my CheckIn Model:
class CheckIn: NSObject {
let id: String
let firstName: String
let lastName: String
let latitude: String
let longitude: String
let hint: String
init(json: JSON) {
self.id = json[Constants.Models.CheckIn.id].stringValue
self.firstName = json[Constants.Models.CheckIn.firstName].stringValue
self.lastName = json[Constants.Models.CheckIn.lastName].stringValue
self.hint = json[Constants.Models.CheckIn.hint].stringValue
self.latitude = json["location"][Constants.Models.CheckIn.latitude].stringValue
self.longitude = json["location"][Constants.Models.CheckIn.longitude].stringValue
}
}
id field is not unique in CheckIn.
Here's a slightly simplified example that shows how to combine groups that have the same group name.
Here is the UserGroup class. users is now a variable (var) because we will be adding elements to groups to combine them.
class UserGroups: NSObject {
let groupName: String
var users: [String]?
init(groupName: String, users: [String]?) {
self.groupName = groupName
self.users = users
}
}
Here are three groups, two of the share the same group name, Blues.
let group1 = UserGroups(groupName: "Blues", users: ["Tom", "Huck", "Jim"])
let group2 = UserGroups(groupName: "Reds", users: ["Jo", "Ben", "Tommy"])
let group3 = UserGroups(groupName: "Blues", users: ["Polly", "Watson", "Douglas"])
Next, we'll put all the groups in an array.
let allGroups = [group1, group2, group3]
Here, we use Swift's reduce function to allow us to reduce the array to only groups with unique group names.
let compacted = allGroups.reduce([UserGroups](), { partialResult, group in
var dupe = partialResult.filter {$0.groupName == group.groupName }.first
if let dupeGroup = dupe {
dupeGroup.users?.append(contentsOf: group.users ?? [])
return partialResult
} else {
var newPartialResult = partialResult
newPartialResult.append(group)
return newPartialResult
}
})
The array is now reduced to unique groups, we print out all the groups and their users with the help of Swift's map function.
print(compacted.map { $0.users })
// Prints [
Optional(["Tom", "Huck", "Jim", "Polly", "Watson", "Douglas"]),
Optional(["Jo", "Ben", "Tommy"])
]
The Solution
You did not include the CheckIn model, but I will assume that it has some sort of an id field unique to each user. We will use this to make the object Hashable:
// Add this to your file outside of the UserGroups class
extension CheckIn: Hashable {
var hashValue: Int { return self.id }
}
Making it Hashable allows you to convert the Array to a Set, which does not allow duplicates and will remove them in a very efficient way.
// Change getUserGroupsList as follows
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn] {
return Array(Set(jsonArray.flatMap({ (jsonItem: JSON) -> CheckIn in
return CheckIn(json: jsonItem)
})))
}
Optional Considerations
As an aside, in case you're coming from another language, Swift gives you nice type inference and default names for closure arguments ($0 is the first argument). You can probably make the code a little less verbose, but it's a matter of taste which is preferred.
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn] {
return Array(Set(jsonArray.flatMap { CheckIn(json: $0) }))
}
Also consider whether you really want the return value to be an array. If you want the list to always have unique users, it is a bit more efficient to use a Set as your return type and forgo the conversion back to an Array like this:
class func getUserGroupsList(jsonArray: [JSON]) -> Set<CheckIn> {
return Set(jsonArray.flatMap { CheckIn(json: $0) })
}
Finally, consider whether you really need the users property to be optional. With sequence types, it is often sufficient to use an empty sequence to denote absence of a value. Depending on your situation, this may simplify your code. The final version looks like this:
class UserGroups: NSObject {
let groupName: String
let users: Set<CheckIn>
init(json:JSON) {
self.groupName = json[Constants.Models.UserGroups.groupName].stringValue
self.users = UserGroups.getUserGroupsList(jsonArray: json[Constants.Models.UserGroups.users].arrayValue)
}
class func getUserGroupsList(jsonArray: [JSON]) -> Set<CheckIn> {
return Set(jsonArray.flatMap { CheckIn(json: $0) })
}
}
Maintaining Order
The caveat is that Set does not maintain the order of the items. If the order of the groups does matter, we can use this solution instead:
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn] {
var encountered: Set<CheckIn> = []
return jsonArray.flatMap { CheckIn(json: $0) }.filter { encountered.update(with: $0) == nil }
}
In this version, we still use a set, but only to maintain a set of items we've encountered already. The update method on a set returns the same value if it's already in the set or returns nil if it's being inserted for the first time. We use that to filter our array to those items being encountered for the first time while adding them to the set of encountered items to filter them out when they are subsequently encountered again.
I am new to Swift and trying to write my first function that calls a closure that the user passes in. I am having trouble calling my function (which I named fun). I also was unable to find any examples of this online. I just want to call my closure (I am unsure what to pass to it?) and then make a decision based on the boolean result? This seems very easy yet I am not sure.
The goal of the method is to remove duplicates in an array based on the users specifications. In my case I may want to pass in a certain class and an array of it and then remove all classes that have the same name property (ie a name string that matches).
extension Array{
func removeDuplicates<T: Comparable>(fun: (elem: T, arr: [T]) -> Bool) -> [T]
{
var array = [T]()
for element in self
{
if fun(elem: T(), arr: [T])
{
println("hello")
}
}
return array
}
}
This is a slight generalization of Does there exist within Swift's API an easy way to remove duplicate elements from an array? and might be what you are
looking for:
extension Array {
func withoutDuplicates<U : Hashable>(attribute : T -> U) -> [T] {
var result : [T] = []
var seen : Set<U> = Set()
for elem in self {
let value = attribute(elem)
if !seen.contains(value) {
result.append(elem)
seen.insert(value)
}
}
return result
}
}
The attribute closure is applied to each array element, and
a Set is used to keep track of values that occurred already.
Therefore the value type U is required to be Hashable (which
is the case for strings).
Example:
struct Person : Printable {
let firstName : String
let lastName : String
var description: String {
return "\(firstName) \(lastName)"
}
}
let array = [
Person(firstName: "John", lastName: "Doe"),
Person(firstName: "Joe", lastName: "Miller"),
Person(firstName: "Jane", lastName: "Doe")
]
let result = array.withoutDuplicates( { $0.lastName } )
println(result)
// [John Doe, Joe Miller]
An alternative implementation is
func withoutDuplicates<U : Hashable>(attribute : T -> U) -> [T] {
var seen : [U : Bool] = [:]
return self.filter { seen.updateValue(true, forKey: attribute($0)) == nil }
}
which utilizes the fact that the updateValue() method of Dictionary
returns the previous value for the key, and in particular returns nil if the key was not set previously. This is also just a slight generalization of #rintaro's answer to iOS Swift: Filter array to unique items.