diff --git a/contact-list-app.xcodeproj/project.pbxproj b/contact-list-app.xcodeproj/project.pbxproj index fefde2b..88bf50f 100644 --- a/contact-list-app.xcodeproj/project.pbxproj +++ b/contact-list-app.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ BBE208A7241A541600A7D661 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BBE208A5241A541600A7D661 /* LaunchScreen.storyboard */; }; BBE208B2241A541700A7D661 /* contact_list_appTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE208B1241A541700A7D661 /* contact_list_appTests.swift */; }; BBE208BD241A541700A7D661 /* contact_list_appUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE208BC241A541700A7D661 /* contact_list_appUITests.swift */; }; + BBE208CB241ADD6C00A7D661 /* ContactProfileVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE208CA241ADD6C00A7D661 /* ContactProfileVC.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -49,6 +50,7 @@ BBE208B8241A541700A7D661 /* contact-list-appUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "contact-list-appUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; BBE208BC241A541700A7D661 /* contact_list_appUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = contact_list_appUITests.swift; sourceTree = ""; }; BBE208BE241A541700A7D661 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BBE208CA241ADD6C00A7D661 /* ContactProfileVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactProfileVC.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -106,6 +108,7 @@ BBE208A3241A541600A7D661 /* Assets.xcassets */, BBE208A5241A541600A7D661 /* LaunchScreen.storyboard */, BBE208A8241A541600A7D661 /* Info.plist */, + BBE208CA241ADD6C00A7D661 /* ContactProfileVC.swift */, ); path = "contact-list-app"; sourceTree = ""; @@ -262,6 +265,7 @@ BBE2089F241A540F00A7D661 /* ViewController.swift in Sources */, BBE2089B241A540F00A7D661 /* AppDelegate.swift in Sources */, BBE2089D241A540F00A7D661 /* SceneDelegate.swift in Sources */, + BBE208CB241ADD6C00A7D661 /* ContactProfileVC.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/contact-list-app.xcodeproj/xcuserdata/roshka.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/contact-list-app.xcodeproj/xcuserdata/roshka.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 0000000..6fac74f --- /dev/null +++ b/contact-list-app.xcodeproj/xcuserdata/roshka.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/contact-list-app/Assets.xcassets/roshka.imageset/3RAw8Hjd_400x400.png b/contact-list-app/Assets.xcassets/roshka.imageset/3RAw8Hjd_400x400.png new file mode 100644 index 0000000..3a87966 Binary files /dev/null and b/contact-list-app/Assets.xcassets/roshka.imageset/3RAw8Hjd_400x400.png differ diff --git a/contact-list-app/Assets.xcassets/roshka.imageset/Contents.json b/contact-list-app/Assets.xcassets/roshka.imageset/Contents.json new file mode 100644 index 0000000..2c5ac04 --- /dev/null +++ b/contact-list-app/Assets.xcassets/roshka.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "3RAw8Hjd_400x400.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/contact-list-app/Base.lproj/Main.storyboard b/contact-list-app/Base.lproj/Main.storyboard index 25a7638..e6c4f3a 100644 --- a/contact-list-app/Base.lproj/Main.storyboard +++ b/contact-list-app/Base.lproj/Main.storyboard @@ -1,24 +1,210 @@ - + + - - + - + - + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contact-list-app/ContactProfileVC.swift b/contact-list-app/ContactProfileVC.swift new file mode 100644 index 0000000..6cae9ad --- /dev/null +++ b/contact-list-app/ContactProfileVC.swift @@ -0,0 +1,118 @@ + + +import UIKit +import Contacts + +struct ContactProfile{ + var phonNumbersList:[String] + var emailAdressList: [String] +} + +class PhoneNumberCell: UITableViewCell{ + @IBOutlet weak var phoneIconImg: UIImageView! + @IBOutlet weak var phoneNumberLbl: UILabel! +} + +class EmailAddressCell: UITableViewCell{ + @IBOutlet weak var emailAddressLbl: UILabel! +} + +class ContactProfileVC: UIViewController { + + var contact: CNContact? + var contactProfileData:ContactProfile? + @IBOutlet weak var nameLbl: UILabel! + @IBOutlet weak var profilePic: UIImageView! + + @IBOutlet weak var contactPhoneNumbersTable: UITableView! + + override func viewDidLoad() { + super.viewDidLoad() + //call the main function + main() + } + + func main(){ + + //set profile pic + setProfilePic() + + //set contact name + nameLbl.text = "\(contact!.givenName) \(contact!.familyName)" + + //get contact's incoming data + setContactProfileAttributes() + + //set tableView handlers to this instance + contactPhoneNumbersTable.dataSource = self + contactPhoneNumbersTable.delegate = self + } + + func setContactProfileAttributes(){ + var phoneNumbers:[String] = [] + var emailAddressList:[String] = [] + + //get phone numbers + contact!.phoneNumbers.forEach( {phoneNumber in + phoneNumbers.append(phoneNumber.value.stringValue) + }) + + //get emails + contact!.emailAddresses.forEach({emailAddress in + emailAddressList.append(emailAddress.value as String) + }) + + //append to contact's attributes + contactProfileData = ContactProfile(phonNumbersList: phoneNumbers, emailAdressList: emailAddressList) + } + + func setProfilePic(){ + if contact!.imageDataAvailable { + print("imagen disponible") + if let image = UIImage.init(data:contact!.imageData!) { + profilePic.image = image + profilePic.layer.cornerRadius = profilePic.frame.width/2 + } + }else{ + print("no image data available") + } + } + +} + +extension ContactProfileVC: UITableViewDelegate, UITableViewDataSource{ + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + if indexPath.section == 0{//phones + let cell = tableView.dequeueReusableCell(withIdentifier: "contactPhoneNumberCell") as! PhoneNumberCell + cell.phoneNumberLbl.text = contactProfileData!.phonNumbersList[indexPath.row] + return cell + }else{ //emails + let cell = tableView.dequeueReusableCell(withIdentifier: "emailAddressCellID") as! EmailAddressCell + cell.emailAddressLbl.text = contactProfileData!.emailAdressList[indexPath.row] + return cell + } + } + + func numberOfSections(in tableView: UITableView) -> Int { + return 2 + } + + func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + if section == 0 { + return "Phones" + }else{ + return "Emails" + } + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if section == 0 { + return contactProfileData!.phonNumbersList.count + }else{ + return contactProfileData!.emailAdressList.count + } + + } +} diff --git a/contact-list-app/Info.plist b/contact-list-app/Info.plist index 2a3483c..b0447a4 100644 --- a/contact-list-app/Info.plist +++ b/contact-list-app/Info.plist @@ -2,6 +2,10 @@ + NSPhotoLibraryUsageDescription + $(PRODUCT_NAME) photo use + NSContactsUsageDescription + $(PRODUCT_NAME) contact use CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable diff --git a/contact-list-app/ViewController.swift b/contact-list-app/ViewController.swift index ed38f20..d1a85cd 100644 --- a/contact-list-app/ViewController.swift +++ b/contact-list-app/ViewController.swift @@ -1,20 +1,180 @@ -// -// ViewController.swift -// contact-list-app -// -// Created by Mobile Roshka on 3/12/20. -// Copyright © 2020 Mobile Roshka. All rights reserved. -// import UIKit +import Contacts + +struct ContactList { + var firstLetter:String + var contactList:[CNContact] + var backUpContacts:[CNContact] = [] + var isHidden = false +} + +class ContactCell: UITableViewCell{ + @IBOutlet weak var nameLbl: UILabel! + @IBOutlet weak var profilePic: UIImageView! + +} class ViewController: UIViewController { + let contactStore = CNContactStore() + var contacts = [CNContact]() + var dataSource:[ContactList]? = [] + var backUpContacts:[CNContact] = [] + var isHidden = false + + @IBOutlet weak var contacsTable: UITableView! + override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view. + //call the main functions + main() + } + + func main(){ + //get contacts from device + getAllContacts() + + //sort in alphabetical order + sortContacts() + + //set tableView handlers to this instance + contacsTable.dataSource = self + contacsTable.delegate = self } + func sortContacts(){ + var pivot = contacts[0].givenName.prefix(1) //first letter from first contact + var temp: [CNContact] = [] //temp array to storage contacts + + for i in (0...contacts.count-2){ + if pivot == contacts[i+1].givenName.prefix(1){ + temp.append(contacts[i]) + }else{ + temp.append(contacts[i]) + dataSource!.append(ContactList(firstLetter: String(pivot), contactList: temp)) + pivot = contacts[i+1].givenName.prefix(1) + temp = [] + } + } + + //ask for the last contact + if contacts[contacts.count-1].givenName.prefix(1) == dataSource![dataSource!.count-1].firstLetter{ //last group + dataSource![dataSource!.count-1].contactList.append(contacts[contacts.count-1]) + }else{ //new group + dataSource!.append(ContactList(firstLetter: String(pivot), contactList: [contacts[contacts.count-1]])) + } + + + } + + func getAllContacts(){ + + //keys + let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactImageDataKey, CNContactImageDataAvailableKey, CNContactPhoneNumbersKey, CNContactEmailAddressesKey] as [CNKeyDescriptor] + //request + let request = CNContactFetchRequest(keysToFetch: keys) + request.sortOrder = .givenName + + do { + try self.contactStore.enumerateContacts(with: request) { + (contact, stop) in + // Array containing all unified contacts from everywhere + self.contacts.append(contact) + print("success") + } + } + catch { + print("unable to fetch contacts") + } + } + + @objc func foldContacts(_ sender: UIButton) -> Void{ + if let section = sender.tag as? Int { + if dataSource![section].isHidden { //show contacts + dataSource![section].contactList = dataSource![section].backUpContacts + dataSource![section].isHidden = false + }else { //hide contacts + dataSource![section].backUpContacts = dataSource![section].contactList + dataSource![section].contactList = [] + dataSource![section].isHidden = true + } + } + //reload table view + contacsTable.reloadData() + + } + } +extension ViewController: UITableViewDelegate, UITableViewDataSource{ + + func numberOfSections(in tableView: UITableView) -> Int { + return dataSource!.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return dataSource![section].contactList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell" , for: indexPath) as! ContactCell + + cell.nameLbl.text = "\(dataSource![indexPath.section].contactList[indexPath.row].givenName) \(dataSource![indexPath.section].contactList[indexPath.row].familyName)" + + if dataSource![indexPath.section].contactList[indexPath.row].imageDataAvailable { + if let image = UIImage.init(data:dataSource![indexPath.section].contactList[indexPath.row].imageData!) { + cell.profilePic.image = image + cell.profilePic.layer.cornerRadius = cell.profilePic.frame.width/2 + } + }else{ + //poner icono por defecto + cell.profilePic.image = UIImage(systemName: "person.fill") + } + + return cell + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + let headerView = UIView.init(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 200)) + + headerView.backgroundColor = UIColor.link + //label + let label = UILabel() + label.frame = CGRect.init(x: 30, y: 10, width: headerView.frame.width-10, height: 25) + label.textColor = UIColor.white + label.text = dataSource![section].firstLetter + label.font = UIFont.boldSystemFont(ofSize: 16.0) + + //button + let foldButton = UIButton() + foldButton.frame = CGRect(x: headerView.frame.width-80, y: 10, width: 30, height: 30) + foldButton.backgroundColor = UIColor.white + foldButton.setTitle("+", for: .normal) + foldButton.layer.cornerRadius = foldButton.frame.width/2 + foldButton.tag = section + foldButton.addTarget(self, action: #selector(foldContacts(_ :)), for: .touchUpInside) + + //add subview to container + headerView.addSubview(label) + headerView.addSubview(foldButton) + + return headerView + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 50 + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return 60 + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + + let contacProfileVC = self.storyboard?.instantiateViewController(withIdentifier: "contactProfileID") as! ContactProfileVC + contacProfileVC.contact = dataSource![indexPath.section].contactList[indexPath.row] + show(contacProfileVC, sender: nil) + } +}