I have a task to show the contact editing screen at once during his appearance (such as WhatsApp), I show him the following way.
#objc private func presentContactEditController() {
guard var contact = contactModel.contact else { return }
if !contact.areKeysAvailable([CNContactViewController.descriptorForRequiredKeys()]) {
do {
let contactStore = CNContactStore()
contact = try contactStore.unifiedContact(withIdentifier: contact.identifier, keysToFetch: [CNContactViewController.descriptorForRequiredKeys()])
} catch {
debugPrint("presentContactEditController error", error.localizedDescription)
}
}
let cnContactViewController = CNContactViewController(for: contact)
cnContactViewController.delegate = self
cnContactViewController.setEditing(true, animated: false)
let contactNaviController = UINavigationController(rootViewController: cnContactViewController)
present(contactNaviController, animated: true, completion: nil)
}
But there is a screen with information about this contact. So I tried to do it through the heir of CNContactViewController, different methods ViewController life cycle, but it works only in viewDidAppear method, but it will be visible to the user. How can I solve this problem? Thank you.
just change let cnContactViewController = CNContactViewController(for: contact)
to
let cvc = CNContactViewController(forNewContact: contact)
it will work for you
I came to the conclusion that that WhatsApp create a custom screen for this purpose. Just I saw the same screen in Telegram only with a modified design.
Related
I'm trying to implement functionality to respond to FORCE_CHANGE_PASSWORD on my iOS app that uses AWS Cognito. I used this Stack Overflow question which references this sample code. Right now, my code opens a view controller like it's supposed to; however, once on that view controller, I can't get it to do anything. In the sample code, it seems that when you want to submit the password change request you call .set on an instance of AWSTaskCompletionSource<AWSCognitoIdentityNewPasswordRequiredDetails>, yet when I do this, the protocol function didCompleteNewPasswordStepWithError is never called. Interestingly, the other protocol function getNewPasswordDetails is called quickly after viewDidLoad and I can't tell why. I believe this shouldn't be called until the user has entered their new password, etc and should be in response to .set but I could be wrong.
My code is pretty identical to the sample code and that SO post, so I'm not sure what's going wrong here.
My relevant AppDelegate code is here:
extension AppDelegate: AWSCognitoIdentityInteractiveAuthenticationDelegate {
func startNewPasswordRequired() -> AWSCognitoIdentityNewPasswordRequired {
//assume we are presenting from login vc cuz where else would we be presenting that from
DispatchQueue.main.async {
let presentVC = UIApplication.shared.keyWindow?.visibleViewController
TransitionHelperFunctions.presentResetPasswordViewController(viewController: presentVC!)
print(1)
}
var vcToReturn: ResetPasswordViewController?
returnVC { (vc) in
vcToReturn = vc
print(2)
}
print(3)
return vcToReturn!
}
//put this into its own func so we can call it on main thread
func returnVC(completion: #escaping (ResetPasswordViewController) -> () ) {
DispatchQueue.main.sync {
let storyboard = UIStoryboard(name: "ResetPassword", bundle: nil)
let resetVC = storyboard.instantiateViewController(withIdentifier: "ResetPasswordViewController") as? ResetPasswordViewController
completion(resetVC!)
}
}
}
My relevant ResetPasswordViewController code is here:
class ResetPasswordViewController: UIViewController, UITextFieldDelegate {
#IBAction func resetButtonPressed(_ sender: Any) {
var userAttributes: [String:String] = [:]
userAttributes["given_name"] = firstNameField.text!
userAttributes["family_name"] = lastNameField.text!
let details = AWSCognitoIdentityNewPasswordRequiredDetails(proposedPassword: self.passwordTextField.text!, userAttributes: userAttributes)
self.newPasswordCompletion?.set(result: details)
}
}
extension ResetPasswordViewController: AWSCognitoIdentityNewPasswordRequired {
func getNewPasswordDetails(_ newPasswordRequiredInput: AWSCognitoIdentityNewPasswordRequiredInput, newPasswordRequiredCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityNewPasswordRequiredDetails>) {
self.newPasswordCompletion = newPasswordRequiredCompletionSource
}
func didCompleteNewPasswordStepWithError(_ error: Error?) {
DispatchQueue.main.async {
if let error = error as? NSError {
print("error")
print(error)
} else {
// Handle success, in my case simply dismiss the view controller
SCLAlertViewHelperFunctions.displaySuccessAlertView(timeoutValue: 5.0, title: "Success", subTitle: "You can now login with your new passowrd", colorStyle: Constants.UIntColors.emeraldColor, colorTextButton: Constants.UIntColors.whiteColor)
self.dismiss(animated: true, completion: nil)
}
}
}
}
Thank you so much for your help in advance and let me know if you need any more information.
I searched now for at least 2 hours and couldn't find an answer for this.
I need to have an object of type CNContact. I found the CNContactViewController and this view would meet perfectly my requirements. With it I can create a new contact with the predefined view and this gives me an CNContact object.
This is my problem:
The CNContactViewController saves the created contact to the contactStore. But I don't want this. Can I suppress this behaviour? I'm pretty sure there must be a solution.
This is my code for creation of the ViewController:
let contactController = CNContactViewController(forNewContact: nil)
contactController.allowsEditing = true
contactController.allowsActions = false
contactController.displayedPropertyKeys = [CNContactPostalAddressesKey, CNContactPhoneNumbersKey, CNContactGivenNameKey]
contactController.delegate = self
contactController.view.layoutIfNeeded()
let navigationController = UINavigationController(rootViewController: contactController)
self.present(navigationController, animated: true)
Here I would like to use the contact without having it in the Addressbook:
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
viewController.navigationController?.dismiss(animated: true)
}
Thanks for your help!
But I don't want this. Can I suppress this behavior? I'm pretty sure there must be a solution.
There isn’t. If the user saves the edited contact (taps Done) then by the time you hear about it, it has been saved into the contacts database and there is nothing you can do about it.
Of course you can turn right around and delete the saved contact. But you cannot prevent the saving from taking place to begin with.
I'm doing something like this:
let contact = CNMutableContact()
contact.contactType = .person
contact.givenName = "giveName"
let cvc = CNContactViewController(forUnknownContact: contact)
cvc.delegate = self
cvc.contactStore = CNContactStore()
cvc.allowsEditing = false
self.navigationController?.pushViewController(cvc, animated: true)
You can allow the user to save the contact to the internal contact store - but it is not stored automatically.
Remember to implement the delegate functions (fx as an extension)
extension MyContactsViewController: CNContactViewControllerDelegate {
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
viewController.dismiss(animated: true, completion: nil)
}
func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool {
print(property.key)
return true
}
}
I have been working on this problem for a bit but can't get anything to work.
I have a view controller that effectively stands along in the main story board. Inside that view controller, when a specific action occurs that passes a contact type to the action.
The action opens a new contact view controller for the contact passed, and allows the user to interact with the contact per the CNContactViewController.
Once the user is done with the contact, it will close, because of the didCompleteWith method, but there is no way to exit the contact controller, without actually creating the contact.
//change to new contact screen
let con = CNMutableContact()
con.givenName = familyComponents[0]
con.familyName = familyComponents[1]
let newEmail = CNLabeledValue(label: "Personal", value: objectComponents[2]
as NSString)
con.emailAddresses.append(newEmail)
con.phoneNumbers.append(CNLabeledValue(
label: "Home", value: CNPhoneNumber(stringValue: objectComponents[3])))
let newScreen = CNContactViewController(forUnknownContact: con)
newScreen.message = "Made using app"
newScreen.contactStore = CNContactStore()
newScreen.delegate = self
newScreen.allowsActions = true
let navigationController = UINavigationController(rootViewController:
newScreen)
self.present(navigationController, animated: true, completion: nil)
I have tried everything from navigationItems to navigationItems, it seems to me that the navigation bar is there, because it is possible to change its translucency with this line of code:
navigationController.navigationBar.isTranslucent = true
Methods Below:
func contactViewController(_ viewController: CNContactViewController,
didCompleteWith contact: CNContact?) {
viewController.dismiss(animated: true, completion: nil)
inContact = false
}
func contactViewController(_ shouldPerformDefaultActionForviewController:
CNContactViewController, shouldPerformDefaultActionFor property:
CNContactProperty) -> Bool {
return false
}
Any suggestions would be greatly appreciated, I have been struggling with this problem for too long.
Thanks!
SOLVED:
Got it to work. Turns out that this is a bug in apples framework. To solve this problem I just switched it from Unknown Contact to New Contact, which apparently does not have this bug.
Part of new code:
let newScreen = CNContactViewController(forNewContact: con)
newScreen.delegate = self
let navigationController = UINavigationController(rootViewController: newScreen)
self.present(navigationController, animated: true, completion: nil)
Thanks
can you try with this code
let con = CNMutableContact()
con.givenName = familyComponents[0]
con.familyName = familyComponents[1]
let newEmail = CNLabeledValue(label: "Personal", value: objectComponents[2]
as NSString)
con.emailAddresses.append(newEmail)
con.phoneNumbers.append(CNLabeledValue(
label: "Home", value: CNPhoneNumber(stringValue: objectComponents[3])))
let unkvc = CNContactViewController(forUnknownContact: con)
unkvc.delegate = self
unkvc.allowsEditing = true
unkvc.allowsActions = true
unkvc.title = "Edit Contact"
self.navigationController?.isNavigationBarHidden = false
self.navigationController?.navigationItem.hidesBackButton = true
self.navigationController?.pushViewController(unkvc, animated: false)
I'm using the MFMessageComposeViewController and the MFMailComposeViewController. For some reason only the Mail VC is being styled with the colors I want. Here is how I am styling the Navigation bar in the AppDelegate inside the didFinish func.
let navigationBarAppearace = UINavigationBar.appearance()
navigationBarAppearace.tintColor = Styles.whiteColor()
navigationBarAppearace.barTintColor = Styles.inputColor()
navigationBarAppearace.titleTextAttributes = [NSForegroundColorAttributeName:Styles.whiteColor()]
navigationBarAppearace.isTranslucent = false
But the Message VC is not being styled by the AppDelegate but I'm not sure why not.
I tried this but nothing changed.
let controller = MFMessageComposeViewController()
controller.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: Styles.positiveColor()]
controller.navigationBar.barTintColor = Styles.negativeColor()
controller.messageComposeDelegate = self
Is the Message VC styled differently? It still shows up with the default white nav bar and the default blue cancel button.
Here is a photo of the Email VC and the Message VC navigations bars.
As you can see the Message VC is not being styled like the Email VC Navigation bar, but I'm not sure why.
You can create a subclass of UINavigationBar (MyNavigationBar) where you set all needed properties.
Then, as MFMessageComposeViewController inherits from UINavigationController, you can use its initialization method
init(navigationBarClass: AnyClass?, toolbarClass: AnyClass?)
and provide MyNavigationBar class as a parameter.
The following is for Swift 3/4.
I tried many ways shown on StackOverflow and other sites, including the subclass way mentioned in the above answer. But could not get success in changing color or changing font color of UIBarButtons.
Then tried different way of presenting the MFMessageComposeViewController.
// Configures and returns a MFMessageComposeViewController instance. This is same with no change.
func configuredMessageComposeViewController() -> MFMessageComposeViewController {
let messageComposeVC = MFMessageComposeViewController()
let fileManager:FileManager = FileManager.default
messageComposeVC.messageComposeDelegate = self // Make sure to set this property to self, so that the controller can be dismissed!
messageComposeVC.recipients = [myContactPhone]
if fileManager.fileExists(atPath: mySendImagePath) {
if let image = UIImage(contentsOfFile: mySendImagePath) {
if UIImagePNGRepresentation(image) != nil
{
let imageData1: Data = UIImagePNGRepresentation(image)!
let success = messageComposeVC.addAttachmentData(imageData1, typeIdentifier: "public.data", filename: "image.JPG")
if(success)
{
}
else{
}
}
}
}
return messageComposeVC
}
// Following code is usage of above.
if (MFMessageComposeViewController.canSendText()) {
myMessageComposeVC = configuredMessageComposeViewController()
// old code - Instead of using following way
//present(messageComposeVC, animated: true, completion: nil)
// Used this way to use existing navigation bar.
if let messageComposeVC = myMessageComposeVC {
messageComposeVC.willMove(toParentViewController: self)
messageComposeVC.view.frame = self.view.frame
self.view.addSubview(messageComposeVC.view)
self.addChildViewController(messageComposeVC)
messageComposeVC.didMove(toParentViewController: self)
}
} else {
showSendMMSErrorAlert()
return
}
// Following code to remove it when returned through delegate.
func messageComposeViewController(_ controller: MFMessageComposeViewController, didFinishWith result: MessageComposeResult) {
// old code
//controller.dismiss(animated: true, completion: nil)
controller.willMove(toParentViewController: nil)
controller.view.removeFromSuperview()
controller.removeFromParentViewController()
if(result.rawValue == 0)
{
... error ...
} else {
... success ...
}
}
Hope, this is useful for persons like me.
Regards.
I have a TableView that present a contact.
When I click the cell i would like to do straight to Editable view, instead of going to the CNContactViewController and press "edit Button".
Now i have the following :
firstview
secondview
I would like to skip the second step. Is that possible?
the code I'm using is the same from apple's:
let contacts = try contactStore.unifiedContactWithIdentifier
(contactIdentifier!, keysToFetch: toFetch)
let controller = CNContactViewController(forContact: contacts)
controller.contactStore = contactStore
controller.delegate = self
controller.editing = true
navigationController?
.pushViewController(controller, animated: true)
print(controller.editButtonItem())
} catch {
print(error)
}
EDIT: More or less, to illustrate, what Im trying to do, is the same as WhatsApp has in their App!, thanks!!
Have you tried this approach ?
let toFetch = [CNContactViewController.descriptorForRequiredKeys()]
let contacts = try contactStore.unifiedContactWithIdentifier
(contactIdentifier!, keysToFetch: toFetch)
let contactsViewController = CNContactViewController(forNewContact: contacts)
contactsViewController.delegate = self
contactsViewController.title = "Edit contact"
contactsViewController.contactStore = contactStore
self.navigationController?.pushViewController(contactsViewController, animated: true)