Merge duplicate CNCONTACT array swift 3 contacts framework - ios

I did find duplicate contacts list from this method , now i'm stuck in merging the duplicates, any idea how i can do this.
I fetched duplicate using this code Referenced from previous question.
let formatter = CNContactFormatter()
formatter.style = .fullName
let keys = [CNContactIdentifierKey as CNKeyDescriptor, CNContactFormatter.descriptorForRequiredKeys(for: .fullName)]
let request = CNContactFetchRequest(keysToFetch: keys)
var contactsByName = [String: [CNContact]]()
try! self.store.enumerateContacts(with: request) { contact, stop in
guard let name = formatter.string(from: contact) else { return }
contactsByName[name] = (contactsByName[name] ?? []) + [contact] // or in Swift 4, `contactsByName[name, default: []].append(contact)`
}
let duplicates = contactsByName.filter { $1.count > 1 }

If you followed my previous answer for fetching duplicates list after you can use this code to merge duplicates.
func mergeAllDuplicates() -> CNContact {
let duplicates: [Array<CNContact>] = //Array of Duplicates Contacts
for item in duplicates {
// CNCONTACT PROPERTIES
var namePrefix: [String] = [String]()
var givenName: [String] = [String]()
var middleName: [String] = [String]()
var familyName: [String] = [String]()
var previousFamilyName: [String] = [String]()
var nameSuffix: [String] = [String]()
var nickname: [String] = [String]()
var organizationName: [String] = [String]()
var departmentName: [String] = [String]()
var jobTitle: [String] = [String]()
var phoneNumbers: [CNPhoneNumber] = [CNPhoneNumber]()
var emailAddresses: [NSString] = [NSString]()
var postalAddresses: [CNPostalAddress] = [CNPostalAddress]()
var urlAddresses: [NSString] = [NSString]()
var contactRelations: [CNContactRelation] = [CNContactRelation]()
var socialProfiles: [CNSocialProfile] = [CNSocialProfile]()
var instantMessageAddresses: [CNInstantMessageAddress] = [CNInstantMessageAddress]()
// Filter
for items in item {
namePrefix.append(items.namePrefix)
givenName.append(items.givenName)
middleName.append(items.middleName)
familyName.append(items.familyName)
previousFamilyName.append(items.previousFamilyName)
nameSuffix.append(items.nameSuffix)
nickname.append(items.nickname)
organizationName.append(items.organizationName)
departmentName.append(items.departmentName)
jobTitle.append(items.jobTitle)
for number in items.phoneNumbers {
phoneNumbers.append(number.value)
}
for email in items.emailAddresses {
emailAddresses.append(email.value)
}
for postal in items.postalAddresses {
postalAddresses.append(postal.value)
}
for url in items.urlAddresses {
urlAddresses.append(url.value)
}
for relation in items.contactRelations {
contactRelations.append(relation.value)
}
for social in items.socialProfiles {
socialProfiles.append(social.value)
}
for message in items.instantMessageAddresses {
instantMessageAddresses.append(message.value)
}
}
let newContact = CNMutableContact()
newContact.namePrefix = Array(Set(namePrefix))[0]
newContact.givenName = Array(Set(givenName))[0]
newContact.middleName = Array(Set(middleName))[0]
newContact.familyName = Array(Set(familyName))[0]
newContact.previousFamilyName = Array(Set(previousFamilyName))[0]
newContact.nameSuffix = Array(Set(nameSuffix))[0]
newContact.nickname = Array(Set(nickname))[0]
newContact.organizationName = Array(Set(namePrefix))[0]
newContact.departmentName = Array(Set(namePrefix))[0]
newContact.jobTitle = Array(Set(namePrefix))[0]
for item in Array(Set(phoneNumbers)) {
newContact.phoneNumbers.append(CNLabeledValue(label: CNLabelHome, value: item))
}
for item in Array(Set(emailAddresses)) {
newContact.emailAddresses.append(CNLabeledValue(label: CNLabelHome, value: item))
}
for item in Array(Set(postalAddresses)) {
newContact.postalAddresses.append(CNLabeledValue(label: CNLabelHome, value: item))
}
for item in Array(Set(urlAddresses)) {
newContact.urlAddresses.append(CNLabeledValue(label: CNLabelHome, value: item))
}
for item in Array(Set(contactRelations)) {
newContact.contactRelations.append(CNLabeledValue(label: CNLabelHome, value: item))
}
for item in Array(Set(socialProfiles)) {
newContact.socialProfiles.append(CNLabeledValue(label: CNLabelHome, value: item))
}
for item in Array(Set(instantMessageAddresses)) {
newContact.instantMessageAddresses.append(CNLabeledValue(label: CNLabelHome, value: item))
}
return newContact
}
}
This approach will take quite a memory so I suggest, use this approach as a reference.

I modified this a little bit. Maybe it helps..
extension Array where Element == String {
var bestElement: String? {
var options: [String : Int] = [:]
for element in self {
if let result = options[element] {
options[element] = result + 1
} else {
options[element] = 1
}
}
return options.sorted { $0.1 > $1.1 }.first?.key
}
}
static func merge(duplicates: [CNContact]) -> CNContact {
// EMPTY CNCONTACT PROPERTIES
var givenName: [String] = []
var familyName: [String] = []
var organizationName: [String] = []
var notes: [String] = []
var phoneNumbers: [CNLabeledValue<CNPhoneNumber>] = []
var emailAddresses: [CNLabeledValue<NSString>] = []
var postalAddresses: [CNLabeledValue<CNPostalAddress>] = []
var urlAddresses: [CNLabeledValue<NSString>] = []
// COLLECT VALUES
for contact in duplicates {
givenName.append(contact.givenName)
familyName.append(contact.familyName)
organizationName.append(contact.organizationName)
notes.append(contact.note)
contact.phoneNumbers.forEach { phoneNumbers.append($0) }
contact.emailAddresses.forEach { emailAddresses.append($0) }
contact.postalAddresses.forEach { postalAddresses.append($0) }
contact.urlAddresses.forEach { urlAddresses.append($0) }
}
// MERGE TO NEW CONTACT
let newContact = CNMutableContact()
newContact.givenName = givenName.bestElement ?? ""
newContact.familyName = familyName.bestElement ?? ""
newContact.organizationName = organizationName.bestElement ?? ""
newContact.note = notes.joined(separator: "\n")
newContact.phoneNumbers = phoneNumbers
newContact.emailAddresses = emailAddresses
newContact.postalAddresses = postalAddresses
newContact.urlAddresses = urlAddresses
return newContact
}

Two things I'm asking myself while reading your function.
newContact.phoneNumbers: It seems like you append ALL the numbers from the 2 contacts. If the 2 duplicated contacts have the same number, then the newContact will then have the same number twice in it's list, right?
It feels like the newContact you are creating is losing lots of information. Like nickname or prefix (Doctor, etc…).
Thanks for this compact code anyway :)

Related

The array of String property in a managed object constantly nil

First, I'm extracting the content from the text fields into a dictionary:
var dict: [String: String] = [:]
for metricPair in metricStackView.arrangedSubviews {
if metricPair.subviews[0] is UITextField {
let unitTextField = metricPair.subviews[0] as! UITextField
let valueTextField = metricPair.subviews[1] as! UITextField
if let textContent = unitTextField.text, let valueTextContent = valueTextField.text {
let trimmedKey = textContent.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedValue = valueTextContent.trimmingCharacters(in: .whitespacesAndNewlines)
dict.updateValue(trimmedValue, forKey: trimmedKey)
}
}
}
And I'm saving it to Core Data:
let goal = Goal(context: self.context)
goal.date = Date()
for item in dict {
goal.metrics?.append(item.key)
}
goal.progress.insert(progress)
My managed object looks like this:
extension Goal {
#nonobjc public class func createFetchRequest() -> NSFetchRequest<Goal> {
return NSFetchRequest<Goal>(entityName: "Goal")
}
#NSManaged public var date: Date
#NSManaged public var metrics: [String]?
#NSManaged public var progress: Set<Progress>
}
I keep getting nil for the metrics property of [String] type even before the context is saved. When I log item.key in:
for item in dict {
goal.metrics?.append(item.key)
}
the content is showing up properly.
As far as I can tell from the above code, metrics array is not initialized.
Try replacing this line:
goal.metrics?.append(item.key)
With these lines:
if case nil = goal.metrics?.append(item.key) {
goal.metrics = [item.key]
}
or with simply these lines:
if goal.metrics == nil {
goal.metrics = []
}
goal.metrics?.append(item.key)

How to populate an object array using a Firebase Database

I'm unable to create a function which returns an array of Product objects from my Firebase database.
I attempted using a switch within the closure to set variables to their respective names, and then instantiate the object after all variables have been set. I tried to play around with the scope of the closure to try and access the "retrieved value". Below is a snapshot of my Firebase Database, my product object, and my function.
func createArray() -> [Product] {
var newArray = [Product]()
let ref = Database.database().reference()
var productName = String()
var productHealth = String()
var productPrice = 0
var productImage = String()
for bigIndex in 0..<7 {
for smallIndex in 0..<4{
ref.child("masterSheet/\(bigIndex)/\(smallIndex)").observeSingleEvent(of: .value) { (snapshot) in let retrievedValue = snapshot.value}
//I used to have a switch statement here that went like
//switch smallIndex{ case 0: productPrice = retrievedValue as! String }
}
let completeProduct = Product(productName: productName, productHealth: productHealth, productPrice: productPrice, productImage: productImage)
newArray.append(completeProduct)
}
return newArray
}
Product Object:
import Foundation
import UIKit
class Product {
var productName: String
var productHealth: String
var productPrice: Int
var productImage: String
init(productName: String, productHealth: String, productPrice: Int, productImage: String ){
self.productName = productName
self.productHealth = productHealth
self.productPrice = productPrice
self.productImage = productImage
}
}
My goal is to produce an array of all of the items in the Database.
You need to fetch the value from smallIndex and set it into modelClass Product like this:
func createArray() -> [Product] {
var newArray = [Product]()
let ref = Database.database().reference()
for bigIndex in 0..<7 {
for smallIndex in 0..<4{
ref.child("masterSheet/\(bigIndex)/\(smallIndex)").observeSingleEvent(of: .value) { (snapshot) in let retrievedValue = snapshot.value}
//I used to have a switch statement here that went like
//switch smallIndex{ case 0: productPrice = retrievedValue as! String
let singleProduct = Product()
singleProduct.productName = "fetch value from small index"
singleProduct.productPrice = retrievedValue
sigleProduct.productHealth = "fetch value from small index"
newArray.append(singleProduct)
}
}
}
return newArray
}
For anyone wondering, this is the solution that ended up working for me :)
var product: [Product] = []
ref = Database.database().reference()
databaseHandle = ref.child("masterSheet").observe(.value) { (snapshot) in
guard let rawData = snapshot.value as? [AnyObject] else { return }
for item in rawData {
guard let itemArray = item as? [AnyObject] else { continue }
var pro = Product()
if itemArray.count > 0 {
pro.productName = itemArray[0] as? String
}
if itemArray.count > 1 {
pro.productHealth = itemArray[1] as? String
}
if itemArray.count > 2 {
pro.productPrice = itemArray[2] as? Int
}
if itemArray.count > 3 {
pro.productImage = itemArray[3] as? String
}
// if you use dict, do like this:
//pro.productImage = itemArray["productImage"] as? String
//pro.productPrice = itemArray["productPrice"] as? Int
product.append(pro)
}
self.tableView.reloadData()
}

How to combine arrays depending on user selection?

I want to see what user selected like the name of book and its assocaited chapters
I did this
struct bookChpt {
var book:[String] = []
var chapter:[[Int]] = []
}
let chptSelected = [bookChpt(book:bookArr,chapter:chptArr)]
var bookArr:[String] = []
var chptArr:[[Int]] = []
I have this in viewDidLoad()
if let bTitle = result.value(forKey: "bookTitle") as? String
{
bookArr.append(bTitle)
}
if let cNo = result.value(forKey: "chpNo") as? [Int]
{
chptArr.append(cNO)
}
print(chptSelected)
I am getting this
bookChpt( book: ["Hobbit", "LOTR"], chapter: [[3,5],4])
but I like to see this
["Hobbit", 3, 5], ["LOTR", 4]
There are a couple of possibilities. You could add a function to the struct to display its contents in the way you want:
struct BookChapter {
var book:[String] = []
var chapter:[[Int]] = []
func display() -> [[Any]] {
var output = [[Any]]()
for i in 0..<book.count {
output.append([book[i], chapter[i]])
}
return output
}
}
Or you could modify the structure of the struct to contain the book and chapters as tuples:
struct BookChapter {
var book:[(String, [Int])]
}
Going a bit further, anywhere you see a loop - such as in the display function above - you might also consider using map to achieve the same thing:
func display() -> Any {
return book.enumerated().map { $0.element + " " + chapter[$0.offset].description }
}
If you use an Dictionary like this, you can print the key and value whatever way you wanted.
var bookChapters = [String: [Int]]()
bookChapters["Hobbit"] = [1,2,3]
bookChapters["Hobbit"]?.append(contentsOf: [4])
for (book, chapter) in bookChapters {
print("\(book): \(chapter)")
}
Change your struct to
struct BookChapt {
var book: String = ""
var chapter: [Int] = []
}
and in viewDidLoad()
var bookName = ""
var chapters:[Int] = []
if let bTitle = result.value(forKey: "bookTitle") as? String
{
bookName = bTitle
}
if let cNo = result.value(forKey: "chpNo") as? [Int]
{
chapters = cNo
}
let chptSelected = BookChapt(book: bookName, chapter: chapters)
print(chptSelected)

How to store an array that is within an another array to a global variable that has been passed from JSON in Swift?

I have a JSON which receives an array from an API call
Within that array are 3 other arrays:
userDetails, userStats, communities
An example of this API call is:
["communities": <__NSArrayI 0x6000002540a0>(
{
id = 5;
name = South;
},
{
id = 13;
name = HurraHarry;
},
{
id = 15;
name = EnclliffeT;
}
)
, "userStats": {
totalDraws = 3;
totalLosses = 10;
totalWins = 1;
}, "userDetails": {
id = 31;
"user_email" = "steve#gmail.com";
"user_name" = "Steve Base";
}]
I would like to store the array userStats in a variable that I can pass to another VC.
I have a global variable var userStatsArray = [AnyObject]() in my class
and the following code deals with the JSON:
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:AnyObject]
print (json!)
if let arr = json?["communities"] as? [[String:String]] {
self.communitiesArray = arr.flatMap { $0["name"]!}
self.communityIdsArray = arr.flatMap { $0["id"]!}
}
if let dict = json?["userDetails"] as? [String:String] {
self.tempPlayerId = [dict["id"]!]
let characterArray = self.tempPlayerId.flatMap { String.CharacterView($0) }
let newPlayerId = String(characterArray)
self.playerId = newPlayerId
}
if let tempArray = json?["userStats"] as? [String:AnyObject]{
print ("here ", tempArray)
}
The print command successfully prints the userStats array with all its headers (totalWins, totalDraws, totalLosses...) -
How do I store this array into my global variable var userStatsArray = [AnyObject]() so I can pass it to another VC?
Better you create one custom class like this, and declare the array with that custom class type. then you cast your userStats object to your custom class type.
class userStats: NSObject {
var totalDraws: NSNumber?
var totalLosses: NSNumber?
var totalWins: NSNumber?
init(totalDraws: NSNumber?, totalLosses: NSNumber?, totalWins: NSNumber?) {
self.totalDraws = totalDraws
self.totalWins = totalWins
self.totalLosses = totalLosses
}
}
var userStatsArray = [userStats]()
// CHANGE YOUR CODE LIKE THIS
let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? [String:AnyObject]
print (json!)
if let arr = json?["communities"] as? [[String:String]] {
self.communitiesArray = arr.flatMap { $0["name"]!}
self.communityIdsArray = arr.flatMap { $0["id"]!}
}
if let dict = json?["userDetails"] as? [String:String] {
self.tempPlayerId = [dict["id"]!]
let characterArray = self.tempPlayerId.flatMap { String.CharacterView($0) }
let newPlayerId = String(characterArray)
self.playerId = newPlayerId
}
if let tempArray = json?["userStats"]as? userStats {
userSytatsArray.append(tempArray)
}
Take a look at ObjectMapper! With that powerful framework you can create the mappable models of your data returned by the API and let it perform the whole work for you :)
Declare your model classes like this:
class UserInfo: Mappable {
var communities : [Community]?
var stats: UserStats?
var details: UserDetails?
required init?(map: Map) {
}
func mapping(map: Map) {
communities <- map["communities"]
stats <- map["userStats"]
details <- map["userDetails"]
}
}
class Community: Mappable {
var id: Int!
var name: String!
required init?(map: Map) {
}
func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
}
}
class UserStats: Mappable {
var totalDraws : Int!
var totalLosses : Int!
var totalWins : Int!
required init?(map: Map) {
}
func mapping(map: Map) {
totalDraws <- map["totalDraws"]
totalLosses <- map["totalLosses"]
totalWins <- map["totalWins"]
}
}
class UserDetails: Mappable {
var id : Int!
var email : String!
var username : String!
required init?(map: Map) {
}
func mapping(map: Map) {
id <- map["id"]
email <- map["user_email"]
username <- map["user_name"]
}
}
And later just:
let user = UserInfo(JSONString: JSONString)

Realm is saving only my most recently added object

I'm trying to save my array of objects in Realm, but Realm appears to be saving only the last object.
This is my model class:
class ContactEntry: Entry {
dynamic var contactId: Int = 0
dynamic var email: String? = ""
dynamic var phone: String? = ""
dynamic var building: String? = ""
dynamic var room: String? = ""
dynamic var contactDescription: String? = ""
dynamic var worktime: String? = ""
dynamic var address: String? = ""
dynamic var personEntries: PersonEntry?
}
This is the base class:
class Entry: Object {
dynamic var id: Int = 0
override static func primaryKey() -> String? { return "id" }
}
This is code I'm using for saving:
func createOrUpdate(entities: [Entity]) {
let entries = toEntries(entities)
let realm = try! Realm()
try! realm.write {
realm.add(entries, update: true)
}
}
func toEntry(entity: Contact) -> ContactEntry {
let entry = ContactEntry()
entry.contactId = entity.id
entry.email = entity.email
entry.phone = entity.phone
entry.building = entity.building
entry.room = entity.room
entry.contactDescription = entity.description
entry.worktime = entity.worktime
entry.address = entity.address
entry.personEntries = personDAO.toEntry(entity.person)
return entry
}
func toEntry(entity: Person) -> PersonEntry {
let entry = PersonEntry()
entry.personId = entity.id
entry.firstname = entity.firstname
entry.middlename = entity.middlename
entry.lastname = entity.lastname
entry.photo = entity.photo
return entry
}
I think that it may be because I have relationship in my model, but I'm not sure why they'd be a problem.
I'm using them as described in the Realm documentation.

Resources