I am working with the following code and need to implement two things:
Get the index of person1
Determine whether the list contains person1
struct person {
var name : String
var id : String
}
var list : [person] = []
let person1 = person.init(name: "pankaj", id: "123")
list.append(person.init(name: "Gaurav", id: "1234"))
list.append(person.init(name: "Naresh", id: "1223"))
list.append(person.init(name: "pankaj", id: "123"))
list.append(person.init(name: "rahul", id: "345"))
You can achieve this by confirming to equatable protocol
struct Person : Equatable{
var name : String
var id : String
// here is the function which return true if compared objects are equal else it returns false
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name && lhs.id == rhs.id
}
}
var list : [Person] = []
list.append(Person.init(name: "Gaurav", id: "1234"))
list.append(Person.init(name: "Naresh", id: "1223"))
list.append(Person.init(name: "pankaj", id: "123"))
list.append(Person.init(name: "rahul", id: "345"))
let person1 = Person.init(name: "pankaj", id: "123")
// here you will be not able to access list.index(of: person1) method until you confirm to Equatable protocol
// here you get index of person1 in list
if let index = list.index(of: person1){
print("matched index is \(index)")
}else{
print("not matched")
}
//you can use this method only if you confirm to equatable protocol
// It returns true if person1 is in list otherwise returns false
list.contains(person1)
Related
I want to add a variable of type View to my struct: User and then later add individual views to my users (as shown in Manuelle). However I get the error "Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements" and "Type 'User' does not conform to protocol 'Equatable'/ Hashable".
struct User : Hashable {
let name: String
let age: Int
let profilePicture, status, icon: String
let view: View
}
struct MyUserView: View {
let users = [
User(name: "Manuelle", age: 23, profilePicture: "person", status: "inactive", icon: "message.fill", view: UserProfileView()),
User(name: "Michael", age: 39, profilePicture: "person", status: "active 12 minutes ago", icon: "square.on.square")
]
var body: some View {
NavigationView {
List {
ForEach(users, id: \.self) { user in
HStack {
Text(user.name)
Image(systemName: user.profilePicture)
}
}
}
}
}
}
Remove let view: View from the User struct, that's the main problem.
Also, you can't supply id: \.self to the ForEach view. id has to be a property that is a unique identifier of the struct it cannot be the struct itself because you'll get a crash when the array of structs changes. You have a few options to fix it:
ForEach(users, id: \.name) { user in
Or better:
struct User : Identifiable {
var id: String {
return name
}
Because then you can simply do:
ForEach(users) { user in
But normally we do this:
struct User : Identifiable {
let id = UUID()
Unless of course the userID comes from a server.
I'm still learning SwiftUI and CoreData and I'm working on a dummy project where essentially I'm trying to create a User Entity on the SignUp View, then upon clicking the SignUp Button, I would like to sequentially Create and Fetch the User data in order to display it inside of my ContentView. I came up with a way to do it, but I'm sure that it's the incorrect way since when I create a new Object, it also creates a bunch of NULL data along with it and I suspect that it's because I'm not giving the UI enouygh time to create, fetch, and display the data in the view. Here is the code I'm working with...
Any help would be greatly appreciated!
NavigationLink(
destination: getDestination(from: admin),
isActive: self.$isSignUpValid){
Text("Sign Up")
.onTapGesture {
createUserObject(company: self.company, name: self.name, username: self.username, password: self.password, photo: self.inputImage, admin: self.admin)
selectedUser = fetchUserDetails(withUser: username)
self.selectedImageArray = imagesFromCoreData(object: selectedUser!.photo!)!
isSignUpValid = true
}
}
//Sets appropriate View destination based on value of Admin variable.
func getDestination(from adminValue: Bool) -> AnyView {
if adminValue == false {
return AnyView(ContentView(selectedUser: self.selectedUser ?? User(context: moc), selectedImageArray: self.selectedImageArray))
}
else {
return AnyView(AdminView(selectedUser: self.selectedUser ?? User(context: moc), selectedImageArray: self.selectedImageArray))
}
}
func createUserObject(company: String, name: String, username: String, password: String, photo: UIImage?, admin: Bool){
//Produces a Data object from an Array of Images.
func coreDataObjectFromImages(image: UIImage) -> Data? {
let dataArray = NSMutableArray()
if let data = image.pngData() {
dataArray.add(data)
}
return try? NSKeyedArchiver.archivedData(withRootObject: dataArray, requiringSecureCoding: true)
}
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let newUser = User(context: context)
newUser.id = UUID()
newUser.admin = admin
newUser.company = company
newUser.name = name
newUser.username = username
newUser.password = password
newUser.photo = coreDataObjectFromImages(image: (photo ?? UIImage(systemName:"person.circle")!))
do {
try context.save()
print("New User Created")
} catch {
print(error)
}
}
func fetchUserDetails(withUser user: String) -> User? {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest = NSFetchRequest<User>(entityName: "User")
fetchRequest.fetchLimit = 1
fetchRequest.predicate = NSPredicate(format: "username == %#", user)
do {
let fetchUser = try context.fetch(fetchRequest)
return fetchUser.first
} catch let fetchError {
print("Failed to fetch: \(fetchError)")
}
return nil
}
I have would like to know if there is a way to merge two ( or more ) objects in one list.
Exemple:
I have this class:
class User {
String name
Integer age
Integer score
}
and I got this method on another class
methodTest() {
User a = new User().with{
it.name = "JACK"
it.age = 20
}
User b = new User().with{
it.name = "JACK"
it.score = 50
}
User c = new User().with{
it.name = "TONY"
it.age = 25
}
User d = new User().with{
it.name = "TONY"
it.score = 30
}
List userList = new ArrayList()
userList.add(a)
userList.add(b)
userList.add(c)
userList.add(d)
}
Tere is a way to get a userList merged by name? Something like :
userList = userList.mergeBy(it.name)
and then get a list of Users with:
[{name:"Jack", age: 20 , score: 50},{name:"TONY", age: 25, score: 30}]
You can use .groupBy to group your list by User.name and then transform it to a List<User> by applying .inject function. Below you can find an example (fixed version the code you have shown us):
import groovy.json.JsonOutput
class User {
String name
Integer age
Integer score
}
User a = new User(name: "JACK", age: 20)
User b = new User(name: "JACK", score: 50)
User c = new User(name: "TONY", age: 25)
User d = new User(name: "TONY", score: 30)
List userList = new ArrayList()
userList.add(a)
userList.add(b)
userList.add(c)
userList.add(d)
List<User> users = userList.groupBy { it.name } // (1)
.values() // (2)
.inject([]) { result, users -> // (3)
result << users.inject(new User()) { User merged, User user -> // (4)
merged.name = user.name ?: merged.name
merged.age = user.age ?: merged.age
merged.score = user.score ?: merged.score
return merged
}
}
println JsonOutput.toJson(users)
Let's see what happens here step-by-step:
(1) userList.groupBy { it.name } produces following map:
[JACK:[User(JACK, 20, null), User(JACK, null, 50)], TONY:[User(TONY, 25, null), User(TONY, null, 30)]]
(2) calling .values() on this map returns a list of list of users:
[[User(JACK, 20, null), User(JACK, null, 50)], [User(TONY, 25, null), User(TONY, null, 30)]]
(3) then .inject([]) { result, users -> /* ... */ } collects every list of users, applies transformation and adds result to result list (we start with empty [] here)
(4) here we call another .inject() function on users list (this users list contains a list of users with same name, e.g. [JACK:[User(JACK, 20, null), User(JACK, null, 50)]). We start with a new "empty" user (.inject(new User())). We access it by merged variable inside the closure - this variable holds the last result of each iteration inside .inject() function. So it starts with this empty user, gets the first one, sets the name and age (score is not set, because it is null), then it gets second user, sets name (same one) and score (age is not set, because in this user has null age). Final User is added to result list using left shift operator <<.
Eventually when you print to console your final users list you will see desired output:
[{"age":20,"score":50,"name":"JACK"},{"age":25,"score":30,"name":"TONY"}]
Final note
Of course you can make this code even simple, e.g. you can add a method to User class that merges two user instances, something like:
import groovy.json.JsonOutput
class User {
String name
Integer age
Integer score
User merge(User user) {
return new User(
name: user.name ?: name,
age: user.age ?: age,
score: user.score ?: score
)
}
}
List<User> userList = [
new User(name: "JACK", age: 20),
new User(name: "JACK", score: 50),
new User(name: "TONY", age: 25),
new User(name: "TONY", score: 30)
]
List<User> users = userList.groupBy { it.name }
.values()
.inject([]) { result, users ->
result << users.inject(new User()) { User merged, User user -> merged.merge(user) }
}
println JsouOutput.toJson(users)
Using some simple groovy magic:
class User{
String name
Integer age
Integer score
String toString(){ "$name:$age:$score" }
}
User a = new User(
name:"JACK",
age : 20
)
User b = new User(
name : "JACK",
score :50
)
User c = new User(
name : "TONY",
age : 25
)
User d = new User(
name : "TONY",
score : 30
)
List userList = [ a, b, c, d ]
def mergedList = userList.inject( [:].withDefault{ new User() } ){ res, User u ->
res[ u.name ].name = u.name
if( u.age ) res[ u.name ].age = u.age
if( u.score ) res[ u.name ].score = u.score
res
}.values()
assert '[JACK:20:50, TONY:25:30]' == mergedList.toString()
I want to check a value is there in section object.This code is working fine but if I write the complete name only it will get in to the filtered object.i need to get the filtered data when the search test matches with a substring in the string array
["A": ["Affenpoo", "Affenpug", "Affenshire", "Affenwich", "Afghan Collie", "Afghan Hound"], "B": ["Bagle Hound", "Boxer"]]
struct Objects {
var sectionName : String!
var sectionObjects : [String]
var sectionid:[String]!
var sectionph:[String]!
var sectionImage:[String]!
}
var objectArray = [Objects]()
var objectArrayFilter = [Objects]()
objectArrayFilter = objectArray.filter({$0.sectionObjects.contains(searchBar.text!)})
If you want filter like that if you enter string afg in UITextField then it should return only two object "Afghan Collie", "Afghan Hound" with section A, then you can make it like this.
objectArrayFilter = objectArray.flatMap {
var filterObjects = $0
filterObjects.sectionObjects = $0.sectionObjects.filter {
$0.range(of : searchBar.text!, options: .caseInsensitive) != nil
}
return filterObjects.sectionObjects.isEmpty ? nil : filterObjects
}
Edit: Struct that you have make is not proper what you need to do is make another struct and make with property object,id,ph and image all type of string and then make array of this struct inside your Object struct.
struct SubObjects {
var sectionObject: String!
var sectionid: String!
var sectionph: String!
var sectionImage: String!
}
struct Objects {
var sectionName : String!
var sectionObjects : [SubObjects]!
}
Now filter this way.
var objectArray = [Objects]()
var objectArrayFilter = [Objects]()
objectArrayFilter = objectArray.flatMap {
var filterObjects = $0
filterObjects.sectionObjects = $0.sectionObjects.filter {
$0.sectionObject.range(of : searchBar.text!, options: .caseInsensitive) != nil
}
return filterObjects.sectionObjects.isEmpty ? nil : filterObjects
}
Please try the following :
objectArrayFilter = objectArray.filter { $0.sectionObjects.contains(where: { $0.contains(searchBar.text!) }) }
This is a follow-up to this question: Can I have a Swift protocol without functions
Suppose I want to add more properties to my protocol:
protocol Nameable {
var name: String {get}
var fullName: String: {get}
var nickName: String: {get}
}
However, not every struct that conforms to this protocol may have a fullName and/or nickName. How do I go about this? Can I make these two properties somehow optional? Or maybe I need three separate protocols? Or do I just add them to each struct, but leave them empty, like this:
struct Person : Nameable {
let name: String
let fullName: String
let nickName: String
let age: Int
// other properties of a Person
}
let person = Person(name: "Steve", fullName: "", nickName: "Stevie", age: 21)
That compiles and works, but I don't know if this is the 'correct' approach?
Unlike in Objective-C, you cannot define optional protocol requirements in pure Swift. Types that conform to protocols must adopt all the requirements specified.
One potential way of allowing for optional property requirements is defining them as optionals, with a default implementation of a computed property that just returns nil.
protocol Nameable {
var name : String? { get }
var fullName : String? { get }
var nickname : String? { get }
}
extension Nameable {
var name : String? { return nil }
var fullName : String? { return nil }
var nickname : String? { return nil }
}
struct Person : Nameable {
// Person now has the option not to implement the protocol requirements,
// as they have default implementations that return nil
// What's cool is you can implement the optional typed property requirements with
// non-optional properties – as this doesn't break the contract with the protocol.
var name : String
}
let p = Person(name: "Allan")
print(p.name) // Allan
However the downside to this approach is that you potentially pollute conforming types with properties that they don't implement (fullName & nickName in this case).
Therefore if it makes no logical sense for a type to have these properties (say you wanted to conform City to Nameable – but cities don't (really) have nicknames), you shouldn't be conforming it to Nameable.
A much more flexible solution, as you say, would be to define multiple protocols in order to define these requirements. That way, types can choose exactly what requirements they want to implement.
protocol Nameable {
var name : String { get }
}
protocol FullNameable {
var fullName : String { get }
}
protocol NickNameable {
// Even types that conform to NickNameable may have instances without nicknames.
var nickname : String? { get }
}
// A City only needs a name, not a fullname or nickname
struct City : Nameable {
var name : String
}
let london = City(name: "London")
// Person can have a name, full-name or nickname
struct Person : Nameable, FullNameable, NickNameable {
var name : String
var fullName: String
var nickname: String?
}
let allan = Person(name: "Allan", fullName: "Allan Doe", nickname: "Al")
You could even use protocol composition in order to define a typealias to represent all three of these protocols for convenience, for example:
typealias CombinedNameable = Nameable & FullNameable & NickNameable
struct Person : CombinedNameable {
var name : String
var fullName: String
var nickname: String?
}
You can give a default implementation to these property using protocol extension and override the property in classes/structs where actually you needed
extension Nameable{
var fullName: String{
return "NoFullName"
}
var nickName: String{
return "NoNickName"
}
}
struct Foo : Nameable{
var name: String
}